S6(하네스 + 마감): 큐/생성기의 볼륨 붕괴 재발을 정적으로 강제하고, S1~S5 사이트맵 개편의 공개 표면을 문서화하며 코어를 7.1.0 으로 확정한다. 하네스(주안점 5): - test-scenario 매니페스트에 scale 축(n≥100000 + assert) 도입. 내장 YAML fallback 파서 제약으로 block-array of flow objects 형태. - 룰 2종: scenario-scale-axis-required(batch 매니페스트 scale 강제), job-generator-needs-scale-test(Jobs·*Generator 변경 시 scale 시나리오 요구). *Command 은 볼륨 비의존이라 제외 — tags:[batch] opt-in 으로 커버. - tests/scenarios/sitemap-generation.yaml + @scale 마킹 3(Writer/Progress/ManagerMode). 문서: seo-system(증분 저장소·모드·진행상황·lazy contributor)·testing-guide(scale 절)· broadcasting(sitemap 채널)·hooks(2훅)·database-guide(loc_hash)·cheatsheet. 버전: 코어 7.0.5→7.1.0(config+env+README+INSTALL+CHANGELOG) + 모듈 board/ecommerce/page requires.g7_version >=7.1.0.
642 lines
21 KiB
PHP
642 lines
21 KiB
PHP
<?php
|
|
|
|
namespace Tests\Unit\Seo;
|
|
|
|
use App\Contracts\Extension\StorageInterface;
|
|
use App\Extension\Storage\CoreStorageDriver;
|
|
use App\Seo\SitemapFileStore;
|
|
use App\Seo\SitemapWriter;
|
|
use App\Seo\SitemapXmlRenderer;
|
|
use Illuminate\Support\Facades\Config;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
|
use Tests\TestCase;
|
|
|
|
/**
|
|
* SitemapWriter 단위 테스트
|
|
*
|
|
* 임계 분할, sitemapindex 생성, gzip, atomic commit(manifest 마지막 기록),
|
|
* stale 파일 정리, 대용량 시 bounded memory 를 검증합니다.
|
|
*/
|
|
class SitemapWriterTest extends TestCase
|
|
{
|
|
private RecordingStorage $storage;
|
|
|
|
/**
|
|
* 테스트 초기화 - 가짜 로컬 디스크와 put 순서 기록 스토리지를 준비합니다.
|
|
*/
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
Config::set('app.locale', 'ko');
|
|
Config::set('app.supported_locales', ['ko']);
|
|
|
|
Storage::fake('local');
|
|
|
|
$this->storage = new RecordingStorage(new CoreStorageDriver('local'));
|
|
}
|
|
|
|
/**
|
|
* 테스트용 writer 를 생성합니다.
|
|
*
|
|
* @param int $urlsPerFile 파일당 URL 수
|
|
* @param bool $gzip gzip 압축 여부
|
|
* @return SitemapWriter writer 인스턴스
|
|
*/
|
|
private function makeWriter(int $urlsPerFile = 10, bool $gzip = false): SitemapWriter
|
|
{
|
|
return new SitemapWriter(
|
|
$this->storage,
|
|
SitemapXmlRenderer::fromConfig(),
|
|
$urlsPerFile,
|
|
$gzip,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 커밋된 live 파일 내용을 읽습니다.
|
|
*
|
|
* @param string $filename 파일명
|
|
* @return string|null 파일 내용
|
|
*/
|
|
private function liveContent(string $filename): ?string
|
|
{
|
|
return $this->storage->get(SitemapFileStore::CATEGORY, SitemapFileStore::LIVE_DIR.'/'.$filename);
|
|
}
|
|
|
|
// ─── 분할 ────────────────────────────────────────────
|
|
|
|
/**
|
|
* URL 수가 임계를 넘으면 자식 파일로 분할되는지 확인합니다.
|
|
*/
|
|
public function test_splits_children_at_threshold(): void
|
|
{
|
|
$writer = $this->makeWriter(urlsPerFile: 10);
|
|
$writer->open();
|
|
|
|
// 25건 / 파일당 10건 → 자식 3개 (10, 10, 5)
|
|
for ($i = 1; $i <= 25; $i++) {
|
|
$writer->addUrl(['loc' => url("/p/{$i}"), 'priority' => 0.5]);
|
|
}
|
|
|
|
$writer->close();
|
|
$meta = $writer->commit();
|
|
|
|
$this->assertSame(3, $meta['child_count']);
|
|
$this->assertSame(25, $meta['url_count']);
|
|
$this->assertSame(10, $meta['children'][0]['url_count']);
|
|
$this->assertSame(10, $meta['children'][1]['url_count']);
|
|
$this->assertSame(5, $meta['children'][2]['url_count']);
|
|
}
|
|
|
|
/**
|
|
* 임계에 정확히 맞아떨어지면 빈 자식 파일이 생기지 않는지 확인합니다.
|
|
*/
|
|
public function test_exact_threshold_does_not_create_empty_child(): void
|
|
{
|
|
$writer = $this->makeWriter(urlsPerFile: 10);
|
|
$writer->open();
|
|
|
|
for ($i = 1; $i <= 20; $i++) {
|
|
$writer->addUrl(['loc' => url("/p/{$i}")]);
|
|
}
|
|
|
|
$writer->close();
|
|
$meta = $writer->commit();
|
|
|
|
$this->assertSame(2, $meta['child_count']);
|
|
}
|
|
|
|
/**
|
|
* 각 자식 파일이 well-formed urlset XML 인지 확인합니다.
|
|
*/
|
|
public function test_children_are_well_formed_urlsets(): void
|
|
{
|
|
$writer = $this->makeWriter(urlsPerFile: 10);
|
|
$writer->open();
|
|
|
|
for ($i = 1; $i <= 25; $i++) {
|
|
$writer->addUrl(['loc' => url("/p/{$i}"), 'changefreq' => 'daily']);
|
|
}
|
|
|
|
$writer->close();
|
|
$meta = $writer->commit();
|
|
|
|
foreach ($meta['children'] as $child) {
|
|
$xml = $this->liveContent($child['file']);
|
|
|
|
$this->assertNotNull($xml);
|
|
$this->assertStringStartsWith('<?xml version="1.0" encoding="UTF-8"?>', $xml);
|
|
$this->assertStringEndsWith('</urlset>', $xml);
|
|
$this->assertLessThanOrEqual(SitemapWriter::HARD_URL_CAP, substr_count($xml, '<loc>'));
|
|
$this->assertNotFalse(simplexml_load_string($xml), '자식 sitemap 이 유효한 XML 이어야 합니다.');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* sitemapindex 가 모든 자식을 절대 URL 로 참조하는지 확인합니다.
|
|
*/
|
|
public function test_index_references_all_children(): void
|
|
{
|
|
$writer = $this->makeWriter(urlsPerFile: 10);
|
|
$writer->open();
|
|
|
|
for ($i = 1; $i <= 25; $i++) {
|
|
$writer->addUrl(['loc' => url("/p/{$i}")]);
|
|
}
|
|
|
|
$writer->close();
|
|
$writer->commit();
|
|
|
|
$index = $this->liveContent(SitemapFileStore::INDEX_FILE);
|
|
|
|
$this->assertNotNull($index);
|
|
$this->assertStringContainsString('<sitemapindex', $index);
|
|
$this->assertSame(3, substr_count($index, '<sitemap>'));
|
|
$this->assertStringContainsString('<loc>'.url('/sitemap-1.xml').'</loc>', $index);
|
|
$this->assertStringContainsString('<loc>'.url('/sitemap-3.xml').'</loc>', $index);
|
|
$this->assertNotFalse(simplexml_load_string($index));
|
|
}
|
|
|
|
/**
|
|
* URL 이 하나도 없어도 빈 urlset 자식 1개와 인덱스를 남기는지 확인합니다.
|
|
*/
|
|
public function test_empty_set_still_emits_index_and_one_child(): void
|
|
{
|
|
$writer = $this->makeWriter();
|
|
$writer->open();
|
|
$writer->close();
|
|
$meta = $writer->commit();
|
|
|
|
$this->assertSame(1, $meta['child_count']);
|
|
$this->assertSame(0, $meta['url_count']);
|
|
|
|
$xml = $this->liveContent('sitemap-1.xml');
|
|
$this->assertStringNotContainsString('<url>', $xml);
|
|
$this->assertNotFalse(simplexml_load_string($xml));
|
|
}
|
|
|
|
/**
|
|
* loc 이 없는 항목은 무시되는지 확인합니다.
|
|
*/
|
|
public function test_entry_without_loc_is_skipped(): void
|
|
{
|
|
$writer = $this->makeWriter();
|
|
$writer->open();
|
|
$writer->addUrl(['changefreq' => 'daily']);
|
|
$writer->close();
|
|
$meta = $writer->commit();
|
|
|
|
$this->assertSame(0, $meta['url_count']);
|
|
}
|
|
|
|
// ─── 다국어 ──────────────────────────────────────────
|
|
|
|
/**
|
|
* 다국어에서는 URL 하나가 로케일 수만큼의 <url> 블록으로 계산되는지 확인합니다.
|
|
*/
|
|
public function test_multilingual_counts_url_blocks_per_locale(): void
|
|
{
|
|
Config::set('app.supported_locales', ['ko', 'en']);
|
|
|
|
$writer = $this->makeWriter(urlsPerFile: 10);
|
|
$writer->open();
|
|
|
|
// 6건 × 2로케일 = 12블록 → 자식 2개
|
|
for ($i = 1; $i <= 6; $i++) {
|
|
$writer->addUrl(['loc' => url("/p/{$i}")]);
|
|
}
|
|
|
|
$writer->close();
|
|
$meta = $writer->commit();
|
|
|
|
$this->assertSame(12, $meta['url_count']);
|
|
$this->assertSame(2, $meta['child_count']);
|
|
}
|
|
|
|
// ─── gzip ────────────────────────────────────────────
|
|
|
|
/**
|
|
* gzip 활성 시 .xml.gz 로 압축 저장되고 인덱스 loc 도 .gz 를 가리키는지 확인합니다.
|
|
*/
|
|
public function test_gzip_writes_compressed_children(): void
|
|
{
|
|
$writer = $this->makeWriter(urlsPerFile: 10, gzip: true);
|
|
$writer->open();
|
|
|
|
for ($i = 1; $i <= 5; $i++) {
|
|
$writer->addUrl(['loc' => url("/p/{$i}")]);
|
|
}
|
|
|
|
$writer->close();
|
|
$meta = $writer->commit();
|
|
|
|
$this->assertTrue($meta['gzip']);
|
|
$this->assertSame('sitemap-1.xml.gz', $meta['children'][0]['file']);
|
|
|
|
$raw = $this->liveContent('sitemap-1.xml.gz');
|
|
$decoded = gzdecode($raw);
|
|
|
|
$this->assertNotFalse($decoded, 'gzip 자식 파일이 해제 가능해야 합니다.');
|
|
$this->assertStringEndsWith('</urlset>', $decoded);
|
|
|
|
// 인덱스는 비압축이며 .gz 자식을 가리킵니다.
|
|
$index = $this->liveContent(SitemapFileStore::INDEX_FILE);
|
|
$this->assertStringContainsString('<loc>'.url('/sitemap-1.xml.gz').'</loc>', $index);
|
|
}
|
|
|
|
/**
|
|
* gzip 비활성이 기본값인지 확인합니다.
|
|
*/
|
|
public function test_gzip_disabled_by_default(): void
|
|
{
|
|
$writer = $this->makeWriter(urlsPerFile: 10);
|
|
$writer->open();
|
|
$writer->addUrl(['loc' => url('/p/1')]);
|
|
$writer->close();
|
|
$meta = $writer->commit();
|
|
|
|
$this->assertFalse($meta['gzip']);
|
|
$this->assertSame('sitemap-1.xml', $meta['children'][0]['file']);
|
|
}
|
|
|
|
// ─── 커밋 ────────────────────────────────────────────
|
|
|
|
/**
|
|
* manifest.json 이 커밋의 마지막 기록인지 확인합니다 (커밋 마커).
|
|
*/
|
|
public function test_manifest_is_written_last(): void
|
|
{
|
|
$writer = $this->makeWriter(urlsPerFile: 10);
|
|
$writer->open();
|
|
|
|
for ($i = 1; $i <= 25; $i++) {
|
|
$writer->addUrl(['loc' => url("/p/{$i}")]);
|
|
}
|
|
|
|
$writer->close();
|
|
$writer->commit();
|
|
|
|
$manifestPath = SitemapFileStore::LIVE_DIR.'/'.SitemapFileStore::MANIFEST_FILE;
|
|
|
|
$this->assertSame($manifestPath, end($this->storage->putOrder));
|
|
|
|
// live 자식/인덱스가 모두 manifest 보다 먼저 기록되었는지 확인
|
|
$manifestIndex = array_search($manifestPath, $this->storage->putOrder, true);
|
|
foreach (['sitemap-1.xml', 'sitemap-2.xml', 'sitemap-3.xml', SitemapFileStore::INDEX_FILE] as $file) {
|
|
$position = array_search(SitemapFileStore::LIVE_DIR.'/'.$file, $this->storage->putOrder, true);
|
|
|
|
$this->assertNotFalse($position, "{$file} 이 live 에 기록되어야 합니다.");
|
|
$this->assertLessThan($manifestIndex, $position);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 커밋 후 임시 디렉토리가 정리되는지 확인합니다.
|
|
*/
|
|
public function test_commit_cleans_tmp_directory(): void
|
|
{
|
|
$writer = $this->makeWriter(urlsPerFile: 10);
|
|
$writer->open();
|
|
$writer->addUrl(['loc' => url('/p/1')]);
|
|
$writer->close();
|
|
$writer->commit();
|
|
|
|
$this->assertSame([], $this->storage->files(SitemapFileStore::CATEGORY, SitemapFileStore::TMP_DIR));
|
|
}
|
|
|
|
/**
|
|
* 이전 생성물 중 새 세트에 없는 자식 파일이 정리되는지 확인합니다.
|
|
*/
|
|
public function test_commit_removes_stale_children(): void
|
|
{
|
|
// 1차: 자식 3개
|
|
$writer = $this->makeWriter(urlsPerFile: 10);
|
|
$writer->open();
|
|
for ($i = 1; $i <= 25; $i++) {
|
|
$writer->addUrl(['loc' => url("/p/{$i}")]);
|
|
}
|
|
$writer->close();
|
|
$writer->commit();
|
|
|
|
$this->assertNotNull($this->liveContent('sitemap-3.xml'));
|
|
|
|
// 2차: 자식 1개로 축소 → sitemap-2/3 은 stale
|
|
$writer = $this->makeWriter(urlsPerFile: 10);
|
|
$writer->open();
|
|
$writer->addUrl(['loc' => url('/p/1')]);
|
|
$writer->close();
|
|
$meta = $writer->commit();
|
|
|
|
$this->assertSame(1, $meta['child_count']);
|
|
$this->assertNotNull($this->liveContent('sitemap-1.xml'));
|
|
$this->assertNull($this->liveContent('sitemap-2.xml'));
|
|
$this->assertNull($this->liveContent('sitemap-3.xml'));
|
|
}
|
|
|
|
/**
|
|
* open() 이 이전 실행의 임시 파일을 정리하는지 확인합니다.
|
|
*/
|
|
public function test_open_clears_previous_tmp_files(): void
|
|
{
|
|
$this->storage->put(SitemapFileStore::CATEGORY, SitemapFileStore::TMP_DIR.'/sitemap-9.xml', 'stale');
|
|
|
|
$writer = $this->makeWriter();
|
|
$writer->open();
|
|
|
|
$this->assertNull($this->storage->get(SitemapFileStore::CATEGORY, SitemapFileStore::TMP_DIR.'/sitemap-9.xml'));
|
|
}
|
|
|
|
/**
|
|
* manifest 에 세트 메타데이터가 기록되는지 확인합니다.
|
|
*/
|
|
public function test_manifest_contains_set_metadata(): void
|
|
{
|
|
$writer = $this->makeWriter(urlsPerFile: 10);
|
|
$writer->open();
|
|
for ($i = 1; $i <= 15; $i++) {
|
|
$writer->addUrl(['loc' => url("/p/{$i}")]);
|
|
}
|
|
$writer->close();
|
|
$writer->commit();
|
|
|
|
$manifest = json_decode($this->liveContent(SitemapFileStore::MANIFEST_FILE), true);
|
|
|
|
$this->assertSame(2, $manifest['child_count']);
|
|
$this->assertSame(15, $manifest['url_count']);
|
|
$this->assertSame(10, $manifest['urls_per_file']);
|
|
$this->assertNotEmpty($manifest['generated_at']);
|
|
$this->assertCount(2, $manifest['children']);
|
|
}
|
|
|
|
// ─── 상한 / 메모리 ────────────────────────────────────
|
|
|
|
/**
|
|
* urlsPerFile 이 프로토콜 상한(50,000)을 넘지 못하도록 클램프되는지 확인합니다.
|
|
*/
|
|
public function test_urls_per_file_is_clamped_to_protocol_cap(): void
|
|
{
|
|
$writer = $this->makeWriter(urlsPerFile: 999999);
|
|
$writer->open();
|
|
$writer->addUrl(['loc' => url('/p/1')]);
|
|
$writer->close();
|
|
$writer->commit();
|
|
|
|
$manifest = json_decode($this->liveContent(SitemapFileStore::MANIFEST_FILE), true);
|
|
|
|
$this->assertSame(SitemapWriter::HARD_URL_CAP, $manifest['urls_per_file']);
|
|
}
|
|
|
|
/**
|
|
* 대용량(120,001건) 생성 시 자식 수가 정확하고 메모리가 유계인지 확인합니다.
|
|
*
|
|
* 전체 in-memory 적재(회귀)라면 URL 수에 비례해 메모리가 증가합니다.
|
|
*
|
|
* @scale n=1500000 asserts=bounded_peak_memory, child_count_correct
|
|
*/
|
|
public function test_large_volume_splits_and_keeps_memory_bounded(): void
|
|
{
|
|
$writer = $this->makeWriter(urlsPerFile: 50000);
|
|
|
|
// peak 는 프로세스 전체의 high-water mark 라, 선행 테스트가 올려둔 peak 때문에
|
|
// 델타가 0 으로 나와 거짓 통과할 수 있다. 이 테스트 구간만 측정하도록 리셋한다.
|
|
memory_reset_peak_usage();
|
|
$before = memory_get_peak_usage(true);
|
|
|
|
$writer->open();
|
|
for ($i = 1; $i <= 120001; $i++) {
|
|
$writer->addUrl(['loc' => url("/p/{$i}"), 'priority' => 0.5]);
|
|
}
|
|
$writer->close();
|
|
$meta = $writer->commit();
|
|
|
|
$growth = memory_get_peak_usage(true) - $before;
|
|
|
|
// 120001 = 50000 + 50000 + 20001
|
|
$this->assertSame(3, $meta['child_count']);
|
|
$this->assertSame(120001, $meta['url_count']);
|
|
$this->assertSame(50000, $meta['children'][0]['url_count']);
|
|
$this->assertSame(50000, $meta['children'][1]['url_count']);
|
|
$this->assertSame(20001, $meta['children'][2]['url_count']);
|
|
|
|
// 자식 파일 1개 버퍼(수 MB) 수준을 넘지 않아야 합니다.
|
|
// 실측 약 10MB (자식 파일 1개 버퍼 수준). 전체 적재 회귀 시 수십~수백 MB 로 튄다.
|
|
$this->assertLessThan(
|
|
64 * 1024 * 1024,
|
|
$growth,
|
|
'메모리 증가가 자식 파일 1개 버퍼 수준을 넘었습니다 (전체 적재 회귀 의심): '.$growth,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* URL 수가 상한에 못 미쳐도 바이트 임계에 도달하면 자식이 분할됩니다.
|
|
*
|
|
* sitemaps.org 는 파일당 50MB 를 제한하므로 URL 수 상한만으로는 프로토콜 위반을 막지 못한다
|
|
* (긴 URL 5만 건이면 50MB 초과). MAX_FILE_BYTES 분기가 그 방어인데, 45MB 를 실제로 채우면
|
|
* 테스트가 과도하게 무거우므로 하위 클래스로 임계값만 낮춰 같은 경로를 검증한다.
|
|
*/
|
|
public function test_byte_threshold_splits_child_even_below_url_cap(): void
|
|
{
|
|
$writer = new SmallByteCapSitemapWriter(
|
|
$this->storage,
|
|
SitemapXmlRenderer::fromConfig(),
|
|
10000, // URL 수 상한 — 이번 케이스에서는 절대 도달하지 않는다
|
|
false,
|
|
);
|
|
|
|
$writer->open();
|
|
// URL 블록 1개가 임계(2KB)를 넘도록 긴 loc 를 쓴다 → 매 건마다 flush
|
|
for ($i = 1; $i <= 3; $i++) {
|
|
$writer->addUrl(['loc' => url('/p/'.str_repeat("long-segment-{$i}-", 200))]);
|
|
}
|
|
$writer->close();
|
|
$meta = $writer->commit();
|
|
|
|
$this->assertSame(3, $meta['url_count']);
|
|
$this->assertSame(
|
|
3,
|
|
$meta['child_count'],
|
|
'URL 수 상한(10000) 미달이어도 바이트 임계 도달 시 자식이 분할돼야 합니다.'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 바이트 임계로 분할된 자식들도 well-formed urlset 이며 전량 보존됩니다.
|
|
*/
|
|
public function test_byte_threshold_split_children_are_well_formed_and_lossless(): void
|
|
{
|
|
$writer = new SmallByteCapSitemapWriter(
|
|
$this->storage,
|
|
SitemapXmlRenderer::fromConfig(),
|
|
10000,
|
|
false,
|
|
);
|
|
|
|
$writer->open();
|
|
$locs = [];
|
|
for ($i = 1; $i <= 3; $i++) {
|
|
$loc = url('/p/'.str_repeat("seg-{$i}-", 200));
|
|
$locs[] = $loc;
|
|
$writer->addUrl(['loc' => $loc]);
|
|
}
|
|
$writer->close();
|
|
$meta = $writer->commit();
|
|
|
|
$combined = '';
|
|
foreach ($meta['children'] as $child) {
|
|
$xml = $this->liveContent($child['file']);
|
|
|
|
$this->assertStringStartsWith('<?xml version="1.0" encoding="UTF-8"?>', $xml);
|
|
$this->assertStringContainsString('</urlset>', $xml);
|
|
$combined .= $xml;
|
|
}
|
|
|
|
// 분할 과정에서 URL 이 유실되면 안 된다
|
|
foreach ($locs as $loc) {
|
|
$this->assertStringContainsString($loc, $combined);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 바이트 임계를 2KB 로 낮춘 writer (테스트 전용).
|
|
*
|
|
* MAX_FILE_BYTES 분기는 static:: 바인딩이므로 상수 재정의만으로 임계값을 바꿀 수 있다.
|
|
*/
|
|
class SmallByteCapSitemapWriter extends SitemapWriter
|
|
{
|
|
public const MAX_FILE_BYTES = 2048;
|
|
}
|
|
|
|
/**
|
|
* put() 호출 경로를 순서대로 기록하는 StorageInterface 데코레이터 (테스트 전용).
|
|
*
|
|
* manifest.json 이 커밋의 마지막 기록인지 검증하기 위해 사용합니다.
|
|
*/
|
|
class RecordingStorage implements StorageInterface
|
|
{
|
|
/**
|
|
* put 이 호출된 경로 목록 (호출 순서)
|
|
*
|
|
* @var array<int, string>
|
|
*/
|
|
public array $putOrder = [];
|
|
|
|
/**
|
|
* RecordingStorage 생성자
|
|
*
|
|
* @param StorageInterface $inner 위임 대상 스토리지
|
|
*/
|
|
public function __construct(private StorageInterface $inner) {}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function put(string $category, string $path, mixed $content): bool
|
|
{
|
|
$this->putOrder[] = $path;
|
|
|
|
return $this->inner->put($category, $path, $content);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function get(string $category, string $path): ?string
|
|
{
|
|
return $this->inner->get($category, $path);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function exists(string $category, string $path): bool
|
|
{
|
|
return $this->inner->exists($category, $path);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function delete(string $category, string $path): bool
|
|
{
|
|
return $this->inner->delete($category, $path);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function url(string $category, string $path): ?string
|
|
{
|
|
return $this->inner->url($category, $path);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function files(string $category, string $directory = ''): array
|
|
{
|
|
return $this->inner->files($category, $directory);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function deleteDirectory(string $category, string $directory = ''): bool
|
|
{
|
|
return $this->inner->deleteDirectory($category, $directory);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function getBasePath(string $category): string
|
|
{
|
|
return $this->inner->getBasePath($category);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function getDisk(): string
|
|
{
|
|
return $this->inner->getDisk();
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function deleteAll(string $category): bool
|
|
{
|
|
return $this->inner->deleteAll($category);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function response(string $category, string $path, string $filename, array $headers = []): ?StreamedResponse
|
|
{
|
|
return $this->inner->response($category, $path, $filename, $headers);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function withDisk(string $disk): static
|
|
{
|
|
$clone = clone $this;
|
|
$clone->inner = $this->inner->withDisk($disk);
|
|
|
|
return $clone;
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function download(string $category, string $path, string $filename, array $headers = []): ?StreamedResponse
|
|
{
|
|
return $this->inner->download($category, $path, $filename, $headers);
|
|
}
|
|
}
|