isolateInstallerBasePath(['.env.example']); * * // 프로젝트 실제 파일을 읽어야 할 때만 realBasePath() 사용 * require_once $this->realBasePath('upgrades/data/7.0.4/migrations/01_Foo.php'); * } * * protected function tearDown(): void * { * $this->releaseInstallerBasePath(); * parent::tearDown(); * } * ``` * * @see CoreUpdateServiceTest::withIsolatedBasePath 콜백 단위 격리 변형 */ trait IsolatesInstallerBasePath { /** 격리 이전의 실제 앱 루트 */ private ?string $realProjectBasePath = null; /** 이번 테스트가 사용하는 임시 앱 루트 */ private ?string $isolatedBasePath = null; /** * 앱 루트를 임시 디렉토리로 전환합니다. * * 인스톨러 코드가 흔히 기대하는 `storage/`, `storage/app/` 을 미리 만들어 둔다. * * @param array $copyFromProject 실제 프로젝트에서 복사해올 파일 (루트 기준 상대경로) * @return string 생성된 임시 앱 루트 절대경로 */ protected function isolateInstallerBasePath(array $copyFromProject = []): string { $this->realProjectBasePath = $this->app->basePath(); $this->isolatedBasePath = sys_get_temp_dir() .DIRECTORY_SEPARATOR.'g7-installer-isolated-'.bin2hex(random_bytes(6)); File::ensureDirectoryExists($this->isolatedBasePath.'/storage/app'); foreach ($copyFromProject as $relative) { $source = $this->realProjectBasePath.DIRECTORY_SEPARATOR.ltrim($relative, '/\\'); if (is_file($source)) { $destination = $this->isolatedBasePath.DIRECTORY_SEPARATOR.ltrim($relative, '/\\'); File::ensureDirectoryExists(dirname($destination)); File::copy($source, $destination); } } $this->app->setBasePath($this->isolatedBasePath); return $this->isolatedBasePath; } /** * 앱 루트를 원래대로 되돌리고 임시 디렉토리를 제거합니다. * * tearDown 에서 호출한다. 호출되지 않아도 실제 프로젝트 파일은 애초에 건드리지 않았으므로 * 남는 것은 임시 디렉토리뿐이다 — 이것이 복원 의존 방식과의 결정적 차이다. */ protected function releaseInstallerBasePath(): void { if ($this->realProjectBasePath !== null) { $this->app->setBasePath($this->realProjectBasePath); } if ($this->isolatedBasePath !== null && is_dir($this->isolatedBasePath)) { File::deleteDirectory($this->isolatedBasePath); } $this->realProjectBasePath = null; $this->isolatedBasePath = null; } /** * 실제 프로젝트 루트 기준 경로를 반환합니다 (격리와 무관). * * 업그레이드 마이그레이션 파일처럼 **실제 소스**를 읽어야 할 때만 사용한다. * * @param string $relative 프로젝트 루트 기준 상대경로 * @return string 절대경로 */ protected function realBasePath(string $relative = ''): string { $root = $this->realProjectBasePath ?? $this->app->basePath(); return $relative === '' ? $root : $root.DIRECTORY_SEPARATOR.ltrim($relative, '/\\'); } }