name('api.public.templates.routes'); * // → GET templates/{identifier}/routes.json (name: api.public.templates.routes) * // → GET templates/{identifier}/routes (name: api.public.templates.routes.extensionless) * ``` */ private static function registerDualSuffix(): void { Route::macro('dualSuffix', function (string $uri, string $suffix, mixed $action): DualRouteProxy { /** @var Router $this */ $base = rtrim($uri, '/'); // 확장자 형태를 먼저 등록 (더 구체적인 패턴이 우선 매칭되어야 함) $extension = $this->get($base.'.'.ltrim($suffix, '.'), $action); $extensionless = $this->get($base, $action); return new DualRouteProxy($extension, $extensionless); }); } /** * `Route::dualSuffixSegment()` 매크로를 등록합니다. * * 접미사 자체가 의미를 담아 **뗄 수 없는** 엔드포인트용입니다. * `bundle.js` 와 `bundle.css` 는 접미사를 제거하면 둘 다 `bundle` 이 되어 충돌하므로, * 접미사를 삭제하는 대신 경로 세그먼트로 내린다. * * ```php * Route::dualSuffixSegment('modules/bundle', 'js', [Ctrl::class, 'serveBundleJs']) * ->name('api.public.modules.bundle.js'); * // → GET modules/bundle.js (name: api.public.modules.bundle.js) * // → GET modules/bundle/js (name: api.public.modules.bundle.js.extensionless) * ``` */ private static function registerDualSuffixSegment(): void { Route::macro('dualSuffixSegment', function (string $uri, string $suffix, mixed $action): DualRouteProxy { /** @var Router $this */ $base = rtrim($uri, '/'); $normalized = ltrim($suffix, '.'); $extension = $this->get($base.'.'.$normalized, $action); $extensionless = $this->get($base.'/'.$normalized, $action); return new DualRouteProxy($extension, $extensionless); }); } /** * `Route::dualAsset()` 매크로를 등록합니다. * * 와일드카드 파일 경로를 갖는 자산 서빙 엔드포인트를 두 형태로 등록합니다. * 확장자 형태는 `{path}` 세그먼트로, 확장자 없는 형태는 `?file=` 쿼리로 경로를 받으며 * 양쪽 모두 동일 컨트롤러로 들어갑니다 (흡수는 각 FormRequest 의 * `prepareForValidation()` 이 담당). * * ```php * Route::dualAsset('templates/assets/{identifier}', [Ctrl::class, 'serveAsset']) * ->name('api.public.templates.assets'); * // → GET templates/assets/{identifier}/{path} where path = .* * // → GET templates/assets/{identifier} (?file=js/a.js) * ``` */ private static function registerDualAsset(): void { Route::macro('dualAsset', function (string $uri, mixed $action): DualRouteProxy { /** @var Router $this */ $base = rtrim($uri, '/'); // 확장자 형태를 먼저 등록 (경로 세그먼트가 더 구체적) $extension = $this->get($base.'/{path}', $action)->where('path', '.*'); $extensionless = $this->get($base, $action); return new DualRouteProxy($extension, $extensionless); }); } }