Ray Lee | 李宗叡
Learn or Die
Published in
6 min readJun 5, 2024

--

# 前言

紀錄 Laravel Scramble 的特殊用法

Laravel Scramble 是一款針對 Laravel 的 API 文件套件,主張 code as document,盡可能的減少 comment 的目標下,產出 API 文件

Scramble 的版本為 v0.8.6

# 情境

# 多份 API 文件

原先公司的產品是單純的 前端 + 後端,也就是 後端 的 API 只單純為產品的 前端 服務,在這個情境下,Laravel Scramble 官方文件上提供的用法已經可以很好的解決問題

後來需求調整,後端 除了要 繼續提供給前端的 API 之外,還要 提供 API 給外部服務 使用。簡單理解,Google 除了要提供 API 給其前端使用外,還需要提供 API 給外部服務使用,在這個情境下,有兩種選擇

  • 整份 code 都切分開來,內部歸內部外部歸外部,這樣自然可以一個專案一份文件。不過這樣做有其成本,這也不在本篇的討論範圍內
  • 文件工具提供分開的 API 文件,可通過不同的 url 來存取不同的文件

# 多版本 API 文件

當產品內有 Mobile APP 時,就會遇到版本問題。原因是 Mobile APP 在版本更新時,是需要先上架一個版本讓 平台 (iOS / Android) 審核,審核通過後,才能上架

而在審核的過程中,舊版本需要持續地提供服務,而審核通過後,新版本需要立即提供服務,於此同時舊版本仍需繼續服務,直到所有使用者皆已升級到新版

所以這時便會需要 同時兩個版本 的 API 存在,自然也會需要 兩份 API 文件

全部都放在同一份文件不行嗎? 自然是不行的,試想,Google 提供的外部 API 如果跟他們自己內用的放在一起,是不是很奇怪?這裡頭也有資安上的考量。

# 問題點

如上所述,Laravel Scramble 並沒有提供多版本多文件的 solution

重新歸納一下問題 & 需求

  • 可以在專案定義不同版本的文件
  • 可以在專案定義不同的 URL,分別提供不同 scope 的 API 文件

# Solution

如上所述,Laravel Scramble 在文件上並沒有提供這樣的 solution,所以要嘛換一套工具,要嘛就只能追一下其 source code,看看能否在現有提供的 method 上,魔改一下,在最低魔改成本的目標下,達到我們的需求

# Source code 研究

原生用法,會通過 URL baseUrl/docs/api 來進到文件頁面,我們以此為入口點,既然有 URL,那勢必有定義相對應的 route

果不其然,在 Laravel Scramble 的 vendor dir path vendor/dedoc/scramble,在 routes/web.php 中有兩支定義好的 route

  • GET docs/api.json
  • View docs/api

baseUrl/docs/api 是由 View docs/api 所提供,所以先從這裡進去看看。這一個 View 的 Blade file,定義了前端 UI,這邊可以看到兩個重點

  • 定義在 config/scramble.php 當中的參數,有部分會影響到 UI 顯示
  • 最重要的資料來源,來自 route('scramble.docs.index')

回到 vendor/dedoc/scramble 一看,route('scramble.docs.index') 不就是 GET docs/api.json 的 route name 嗎?

到這裡,其實已經獲得非常有用的資訊了。整個資料流如下圖

繼續追一下 GET docs/api.json 這支產生資料的 API。code 的細節這邊不贅述,但可以很容易地看到,Scramble 是基於 config/scramble.php 的參數來產生 JSON data

至此,已經有了要魔改的關鍵所需資訊了

  • 既然 UI & Data 都是通過 config/scramble.php 而產生的,那只要在下面兩個時機點去 overwrite config/scramble.php,那自然可以 改變 UI & 改變 JSON Data source
  • (1) 取得 View Blade 時
  • (2) 取得 JSON data source 時
  • 既然可以通過 View docs/api 來取得 API docs page,那也可以自行定義一個 web route 來 render 相同的 Blade

# 實作

首先修改取得 Docs data 的部分。概念上很簡單,如下:

  • 定義一個新的 API route,這個 route 可以帶入 version & scope
  • route 的實作 controller 當中,根據 version & scope 來 overwrite config/scramble.php,使用 Config::set()
  • 完成 config overwrite 之後,直接 return Dedoc\Scramble\Generator,他會去抓當前的 config/scramble.php 的參數,產出相對應的 JSON

完成以上的實作後,我們就可以透過 帶入不同參數API 來取得 render API docs 的 JSON data,接下來,就可以修改 render view 的部分

vendor/dedoc/scramble/routes/web.php 中可以知道,用來 render 的 Blade file 放在 vendor/dedoc/scramble/resources/views/docs.blade.php。copy 這一個 file,然後放到 resources/views/scramble.blade.php,這樣就可以自定義 Blade 裡頭的參數

只需要修改 apiDescriptionUrl 這一個參數,預設的話是讀 config/scramble.php 當中的 route('scramble.docs.index'),但我們需要讓 render file 去讀我們另外定義的 URL,所以可以把這一個參數的值改成 帶入 parameter

<?php
// default
apiDescriptionUrl="{{ route('scramble.docs.index') }}"

// modified
apiDescriptionUrl="{{ $routeUrl }}"

然後我們只要先在外面的 controller 把 $routeUrl 產出,在 pass 給 Blade file 即可。產生 route url 的程式碼如下

<?php
$routeUrl = route('scramble.api.docs.json', $query);

scramble.api.docs.json 為上面定義,用來取得 API Docs data 的 API route name。通過帶入 query 的方式,我們可以取得指定 version or scope 的 API Docs data,再把這個 data 給 Blade file,最終 render 出正確的 API 文件,如下流程圖

--

--

Ray Lee | 李宗叡
Learn or Die

It's Ray. I do both backend and frontend, but more focus on backend. I like coding, and would like to see the whole picture of a product.