「SOAP 不是很多年前的技術了嗎?現在講外部整合,大家講的都是 REST 或 GraphQL,為什麼還要花一篇講 SOAP?」
答案很現實:你不一定能選擇要接什麼協議。這個系統要對接的外部系統,是一個已經運作多年、不受這邊控制的既有系統,它對外開放的介面就是 SOAP,沒有商量的餘地。今天要看的不是「SOAP 語法怎麼寫」,是「在一個現代化的 Laravel 專案裡,怎麼用符合當代慣例的方式,把一個老派協議包裝起來」。
phpro/soap-client 搭配 PSR-18 的傳輸層來接 SOAPFactory::factory() 這種組裝方式,把建構複雜度收斂到一個地方不少既有的機構級系統(校務、金融、政府系統),因為建置年代早、又要求高度穩定性,至今仍然對外提供 SOAP 介面。要跟這類系統整合,沒辦法要求對方換一套協議,只能想辦法在自己這一側,把 SOAP 的複雜度包裝成跟現代 HTTP client 差不多好用的樣子。
這個系統選用的是 phpro/soap-client 這個套件——它讓 SOAP 呼叫可以透過 PSR-18 相容的 HTTP client 傳輸層來發送,而不是直接依賴 PHP 原生的 SoapClient 擴充功能。這代表:昨天講的「介面化、可替換底層實作」的原則,在 SOAP 這種老派協議上一樣適用,不會因為協議本身老舊,就必須放棄現代化的架構原則。
昨天看到的 DataGatewayClientFactory::factory(),實際組裝過程大致是:接收一個 Psr18Transport 和 WSDL 位址,內部用套件提供的 Engine Factory 建出處理 SOAP 通訊細節的物件,再包一層事件分派器(讓每次 SOAP 呼叫可以被監聽、記錄),最後組出一個對外呼叫端只需要呼叫方法名稱、不需要理解底層通訊細節的 Client 物件。
這種「把建構過程的複雜度收斂進一個靜態 Factory 方法」的做法,好處是呼叫端完全不需要知道 SOAP engine、事件分派器這些底層物件怎麼兜起來——只要呼叫 DataGatewayClientFactory::factory($transport, $wsdl),就拿到一個可以直接用的 Client。這跟直接在需要的地方手動組裝一大串物件比起來,可讀性跟可維護性都好上不少。
從呼叫端的角度看,跟 SOAP Client 溝通就像呼叫一個普通物件的方法——例如取得某個清單資料時,呼叫端只需要 $client->getAssigners(),回傳的是解析過的 SOAP 回應物件,呼叫端完全不需要碰到底層的 XML 序列化/反序列化。這正是介面化的價值所在:對接的協議是 SOAP,但寫呼叫這段程式碼的人,體驗上跟呼叫一般的 API Client 沒有太大差別。
❌ 呼叫端直接處理 SOAP 底層細節
$client = new SoapClient($wsdl, ['connection_timeout' => 5]);
$response = $client->__soapCall('GetAssigners', []);
// 呼叫端要理解 SOAP 特有的呼叫慣例,跟其他一般 API 整合的寫法完全不一致
✅ 透過 Factory 組裝,呼叫端只看到乾淨的方法呼叫
$client = DataGatewayClientFactory::factory($transport, $wsdl);
$assigners = $client->getAssigners();
// 呼叫端不需要知道底層是 SOAP,寫法跟呼叫任何一般物件的方法一樣
跟外部系統整合時,「這個外部系統用什麼協議」跟「我這邊的程式碼要寫成什麼樣子」是兩件可以分開處理的事。協議可能老舊、可能受限於外部系統,但自己這一側的架構原則——介面化、不寫死實作、把複雜度收斂到明確的組裝點——不需要因此打折扣。這也是為什麼這篇不是一篇「SOAP 語法教學」,而是想講清楚「怎麼把一個老派協議,用符合現代慣例的方式接進一個現代化的 Laravel 專案」。
你的專案裡有沒有需要對接一個技術比較舊、你沒辦法要求它改變的外部系統?回想一下你目前的接線方式,有沒有因為「協議本身老舊」而讓自己這一側的程式碼也跟著變得難以維護、難以測試?
phpro/soap-client 搭配 PSR-18 傳輸層,讓 SOAP 呼叫也能走介面化、可替換底層實作的路線明天要看這個 SOAP 對接的一段真實演進過程——一開始只顧著「能動」,後來才補上逾時控制,這中間發生了什麼。