StorageInterface::url 의 public 하드코딩을 완화하고 core.storage.filter_url 훅을 신설해, 완전 공개 자산(상품·카테고리·리뷰·에디터 이미지)의 직접 URL 서빙을 옵트인으로 열었다. 미설정 시 기존 스트리밍 100% 보존. 디스크 결정은 확장 개별 설정 > 코어 전역 > 미설정 순이며, 서빙·삭제·이동은 행에 기록된 disk 를 따라 전환 전후 자산이 혼재해도 각자 실제 저장 위치를 향한다. 고아 disk(공급 플러그인 비활성화)는 스트리밍으로 폴백해 500 이 되지 않는다. 공개 이슈 (@lyg-kaban 건의) 대응.
197 lines
4.8 KiB
PHP
197 lines
4.8 KiB
PHP
<?php
|
|
|
|
namespace App\Extension\Storage;
|
|
|
|
use App\Contracts\Extension\StorageInterface;
|
|
use App\Extension\Storage\Concerns\ResolvesPublicUrl;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
|
|
|
/**
|
|
* 코어 스토리지 드라이버
|
|
*
|
|
* 코어 서비스(AttachmentService 등)에서 사용하는 파일 저장소를 제공합니다.
|
|
* 모듈/플러그인과 달리 identifier 없이 {category}/{path} 경로 패턴을 사용합니다.
|
|
*/
|
|
class CoreStorageDriver implements StorageInterface
|
|
{
|
|
use ResolvesPublicUrl;
|
|
|
|
/**
|
|
* 사용할 디스크 이름
|
|
*/
|
|
private string $disk;
|
|
|
|
/**
|
|
* CoreStorageDriver 생성자
|
|
*
|
|
* @param string $disk 디스크 이름 (예: local, public, s3)
|
|
*/
|
|
public function __construct(string $disk = 'local')
|
|
{
|
|
$this->disk = $disk;
|
|
}
|
|
|
|
/**
|
|
* 카테고리와 경로를 조합하여 전체 경로를 생성합니다.
|
|
*
|
|
* @param string $category 카테고리 (빈 문자열이면 경로만 사용)
|
|
* @param string $path 상대 경로
|
|
* @return string 전체 경로 ({category}/{path} 또는 {path})
|
|
*/
|
|
private function resolvePath(string $category, string $path): string
|
|
{
|
|
if (empty($category)) {
|
|
return $path;
|
|
}
|
|
|
|
if (empty($path)) {
|
|
return $category;
|
|
}
|
|
|
|
return "{$category}/{$path}";
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function put(string $category, string $path, mixed $content): bool
|
|
{
|
|
$fullPath = $this->resolvePath($category, $path);
|
|
|
|
return Storage::disk($this->disk)->put($fullPath, $content);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function get(string $category, string $path): ?string
|
|
{
|
|
$fullPath = $this->resolvePath($category, $path);
|
|
|
|
if (! Storage::disk($this->disk)->exists($fullPath)) {
|
|
return null;
|
|
}
|
|
|
|
return Storage::disk($this->disk)->get($fullPath);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function exists(string $category, string $path): bool
|
|
{
|
|
$fullPath = $this->resolvePath($category, $path);
|
|
|
|
return Storage::disk($this->disk)->exists($fullPath);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function delete(string $category, string $path): bool
|
|
{
|
|
$fullPath = $this->resolvePath($category, $path);
|
|
|
|
return Storage::disk($this->disk)->delete($fullPath);
|
|
}
|
|
|
|
/**
|
|
* `core.storage.filter_url` 훅 컨텍스트의 드라이버별 식별 정보를 반환합니다.
|
|
*
|
|
* @return array{scope: string, identifier: ?string} scope + 식별자 (코어는 null)
|
|
*/
|
|
protected function urlHookContext(): array
|
|
{
|
|
return [
|
|
'scope' => 'core',
|
|
'identifier' => null,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function files(string $category, string $directory = ''): array
|
|
{
|
|
$fullPath = $this->resolvePath($category, $directory);
|
|
|
|
return Storage::disk($this->disk)->files($fullPath);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function deleteDirectory(string $category, string $directory = ''): bool
|
|
{
|
|
$fullPath = $this->resolvePath($category, $directory);
|
|
|
|
return Storage::disk($this->disk)->deleteDirectory($fullPath);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function getBasePath(string $category): string
|
|
{
|
|
$fullPath = $this->resolvePath($category, '');
|
|
|
|
return Storage::disk($this->disk)->path($fullPath);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function getDisk(): string
|
|
{
|
|
return $this->disk;
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function deleteAll(string $category): bool
|
|
{
|
|
return $this->deleteDirectory($category, '');
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function response(string $category, string $path, string $filename, array $headers = []): ?StreamedResponse
|
|
{
|
|
$fullPath = $this->resolvePath($category, $path);
|
|
|
|
if (! Storage::disk($this->disk)->exists($fullPath)) {
|
|
return null;
|
|
}
|
|
|
|
return Storage::disk($this->disk)->response($fullPath, $filename, $headers);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function withDisk(string $disk): static
|
|
{
|
|
$clone = clone $this;
|
|
$clone->disk = $disk;
|
|
|
|
return $clone;
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function download(string $category, string $path, string $filename, array $headers = []): ?StreamedResponse
|
|
{
|
|
$fullPath = $this->resolvePath($category, $path);
|
|
|
|
if (! Storage::disk($this->disk)->exists($fullPath)) {
|
|
return null;
|
|
}
|
|
|
|
return Storage::disk($this->disk)->download($fullPath, $filename, $headers);
|
|
}
|
|
}
|