From ebb78c250cd96851b2eeb3555d154c6886f61590 Mon Sep 17 00:00:00 2001 From: Abderrahim GHAZALI Date: Fri, 1 May 2026 12:58:36 +0200 Subject: [PATCH] fix(laravel): expose ReDoc/Scalar via UI switcher in docs view The Laravel docs page only rendered Swagger UI even when ReDoc and Scalar were enabled, since the Blade template never surfaced the `?ui=redoc` / `?ui=scalar` switch present in the Symfony Twig template. Expose the enabled flags to the view, render a header nav switcher in parity with Symfony, and fix the `originalRoute` lookup which relied on the Symfony-only `_route` request attribute and was always null under Laravel. Closes #7682 --- src/Laravel/State/SwaggerUiProcessor.php | 12 +++-- src/Laravel/Tests/DocsSingleUiTest.php | 44 +++++++++++++++++++ src/Laravel/Tests/DocsTest.php | 37 ++++++++++++++++ src/Laravel/public/style.css | 43 ++++++++++++++++++ .../resources/views/swagger-ui.blade.php | 15 ++++++- 5 files changed, 147 insertions(+), 4 deletions(-) create mode 100644 src/Laravel/Tests/DocsSingleUiTest.php diff --git a/src/Laravel/State/SwaggerUiProcessor.php b/src/Laravel/State/SwaggerUiProcessor.php index 30ab151a1b1..f120a817eb5 100644 --- a/src/Laravel/State/SwaggerUiProcessor.php +++ b/src/Laravel/State/SwaggerUiProcessor.php @@ -62,8 +62,8 @@ public function process(mixed $openApi, Operation $operation, array $uriVariable 'formats' => $this->formats, 'title' => $openApi->getInfo()->getTitle(), 'description' => $openApi->getInfo()->getDescription(), - 'originalRoute' => $request->attributes->get('_api_original_route', $request->attributes->get('_route')), - 'originalRouteParams' => $request->attributes->get('_api_original_route_params', $request->attributes->get('_route_params', [])), + 'originalRoute' => $request->attributes->get('_api_original_route') ?? $request->route()?->getName(), + 'originalRouteParams' => $request->attributes->get('_api_original_route_params') ?? $request->route()?->parameters() ?? [], ]; $swaggerData = [ @@ -99,7 +99,13 @@ public function process(mixed $openApi, Operation $operation, array $uriVariable $swaggerData['scalarExtraConfiguration'] = $this->scalarExtraConfiguration; - return new Response(view('api-platform::swagger-ui', $swaggerContext + ['swagger_data' => $swaggerData, 'ui' => $this->getUi()]), 200); + return new Response(view('api-platform::swagger-ui', $swaggerContext + [ + 'swagger_data' => $swaggerData, + 'ui' => $this->getUi(), + 'swaggerUiEnabled' => $this->swaggerEnabled, + 'redocEnabled' => $this->redocEnabled, + 'scalarEnabled' => $this->scalarEnabled, + ]), 200); } /** diff --git a/src/Laravel/Tests/DocsSingleUiTest.php b/src/Laravel/Tests/DocsSingleUiTest.php new file mode 100644 index 00000000000..da3084c775f --- /dev/null +++ b/src/Laravel/Tests/DocsSingleUiTest.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Laravel\Tests; + +use ApiPlatform\Laravel\Test\ApiTestAssertionsTrait; +use Illuminate\Config\Repository; +use Orchestra\Testbench\Concerns\WithWorkbench; +use Orchestra\Testbench\TestCase; + +class DocsSingleUiTest extends TestCase +{ + use ApiTestAssertionsTrait; + use WithWorkbench; + + protected function defineEnvironment($app): void + { + tap($app['config'], static function (Repository $config): void { + $config->set('api-platform.swagger_ui.enabled', true); + $config->set('api-platform.redoc.enabled', false); + $config->set('api-platform.scalar.enabled', false); + }); + } + + public function testHtmlDocsHasNoUiSwitcherWhenOnlyOneUiEnabled(): void + { + $res = $this->get('/api/docs', headers: ['accept' => 'text/html']); + $res->assertOk(); + $content = $res->getContent(); + + $this->assertStringContainsString('init-swagger-ui.js', $content); + $this->assertStringNotContainsString('class="api-platform-ui-switcher"', $content); + } +} diff --git a/src/Laravel/Tests/DocsTest.php b/src/Laravel/Tests/DocsTest.php index 9d155f041d3..afb1cc598bf 100644 --- a/src/Laravel/Tests/DocsTest.php +++ b/src/Laravel/Tests/DocsTest.php @@ -49,4 +49,41 @@ public function testJsonLdAccept(): void $this->assertArrayHasKey('@context', $res->json()); $this->assertSame('application/ld+json; charset=utf-8', $res->headers->get('content-type')); } + + public function testHtmlDocsRendersSwaggerByDefaultWithUiSwitcher(): void + { + $res = $this->get('/api/docs', headers: ['accept' => 'text/html']); + $res->assertOk(); + $content = $res->getContent(); + + $this->assertStringContainsString('class="api-platform-ui-switcher"', $content); + $this->assertStringContainsString('init-swagger-ui.js', $content); + $this->assertStringContainsString('ui=swagger', $content); + $this->assertStringContainsString('ui=redoc', $content); + $this->assertStringContainsString('ui=scalar', $content); + $this->assertMatchesRegularExpression('#]*ui=swagger[^>]*aria-current="page"#', $content); + } + + public function testHtmlDocsRendersRedocWhenRequested(): void + { + $res = $this->get('/api/docs?ui=redoc', headers: ['accept' => 'text/html']); + $res->assertOk(); + $content = $res->getContent(); + + $this->assertStringContainsString('redoc.standalone.js', $content); + $this->assertStringContainsString('init-redoc-ui.js', $content); + $this->assertStringContainsString('class="api-platform-ui-switcher"', $content); + $this->assertMatchesRegularExpression('#]*ui=redoc[^>]*aria-current="page"#', $content); + } + + public function testHtmlDocsRendersScalarWhenRequested(): void + { + $res = $this->get('/api/docs?ui=scalar', headers: ['accept' => 'text/html']); + $res->assertOk(); + $content = $res->getContent(); + + $this->assertStringContainsString('init-scalar-ui.js', $content); + $this->assertStringContainsString('class="api-platform-ui-switcher"', $content); + $this->assertMatchesRegularExpression('#]*ui=scalar[^>]*aria-current="page"#', $content); + } } diff --git a/src/Laravel/public/style.css b/src/Laravel/public/style.css index 64d91d23dfa..265d06b32cc 100644 --- a/src/Laravel/public/style.css +++ b/src/Laravel/public/style.css @@ -2,6 +2,7 @@ html { box-sizing: border-box; overflow: -moz-scrollbars-vertical; overflow-y: scroll; + scroll-padding-top: 70px; } *, @@ -41,6 +42,48 @@ header #logo img { background-color: rgba(40, 134, 144, .4) } +header .api-platform-ui-switcher { + position: fixed; + top: 35px; + left: 50%; + z-index: 102; + transform: translate(-50%, -50%); + display: inline-flex; + padding: 4px; + gap: 2px; + background: rgba(0, 0, 0, .18); + border-radius: 6px; + font-family: 'Open Sans', sans-serif; + font-size: 13px; +} + +header .api-platform-ui-switcher a { + color: rgba(255, 255, 255, .8); + text-decoration: none; + padding: 5px 14px; + border-radius: 4px; + font-weight: 500; + line-height: 1.4; + transition: background-color .15s ease, color .15s ease; +} + +header .api-platform-ui-switcher a:hover { + color: #fff; + background: rgba(255, 255, 255, .12); +} + +header .api-platform-ui-switcher a[aria-current="page"] { + color: #288690; + background: #fff; + cursor: default; + pointer-events: none; +} + +#swagger-ui.api-platform.redoc, +#swagger-ui.api-platform.scalar { + padding-top: 70px; +} + .svg-icons { position:absolute;width:0;height:0 } diff --git a/src/Laravel/resources/views/swagger-ui.blade.php b/src/Laravel/resources/views/swagger-ui.blade.php index ddda146b501..ea5710428d5 100644 --- a/src/Laravel/resources/views/swagger-ui.blade.php +++ b/src/Laravel/resources/views/swagger-ui.blade.php @@ -13,6 +13,19 @@
+ @if ((int) $swaggerUiEnabled + (int) $redocEnabled + (int) $scalarEnabled > 1) + + @endif
@if (config('api-platform.show_webby', true))
@@ -212,7 +225,7 @@ @endif -
+
@if ($ui === 'scalar')