在構建 Laravel 應用程序時,幾乎可以肯定您會在某些時候需要處理會話。它們是 Web 開發的基礎部分。
本文將快速介紹會話是什麼,它們如何在 Laravel 中工作,以及您如何在 Laravel 應用程序中使用它們。
然後,我們將更進一步,深入探討如何使用“會話類”與會話交互,以避免我在處理 Laravel 應用程序時經常遇到的常見陷阱。
最後,我們將了解如何在 Laravel 中測試會話數據。
默認情況下,Web 應用程序是無狀態的,這意味著請求通常彼此不了解。因此,我們需要一種方法來存儲請求之間的數 據。例如,當用戶登錄網站時,我們需要記住他們在訪問期間已登錄。這就是會話的用武之地。
簡而言之,會話是一種在多個請求之間持久保存數據安全的方式。
會話數據可能用於存儲以下內容:
會話數據可以存儲在各種位置,例如:
要了解會話是什麼,讓我們看看它們如何在 Laravel 中工作。
以下是您可能在 Laravel 應用程序的會話中找到的一些示例數據:
<code>[ '_token' => 'bKmSfoegonZLeIe8B6TWvSm1dKwftKsvcT40xaaW' '_previous' => [ 'url' => 'https://my-app.com/users' ] '_flash' => [ 'old' => [ 'success', ], 'new' => [] ] 'success' => 'User created successfully.' 'current_team_id' => 123 ]</code>
讓我們分解每個鍵可能代表的內容。
以下鍵由 Laravel 框架本身添加:
_token
值用於防止 CSRF 攻擊。 _previous.url
值用於存儲先前請求的 URL。 _flash.old
值用於存儲先前請求中閃存會話數據的鍵。在本例中,它表示在先前請求中閃存了 success
值。 _flash.new
值用於存儲當前請求中閃存會話數據的鍵。 以下鍵由我添加:
success
值用於存儲可能顯示給用戶的成功消息。 current_team_id
值用於存儲用戶正在查看的當前團隊的 ID。 默認情況下,Laravel 支持以下會話驅動程序:
cookie
- 會話數據存儲在安全且加密的 Cookie 中。 database
- 會話存儲在您的數據庫中(例如 MySQL、PostgreSQL、SQLite)。 memcached
/ redis
- 會話數據存儲在這些快速的緩存存儲中。 dynamodb
- 會話數據存儲在 AWS DynamoDB 中。 file
- 會話數據存儲在 storage/framework/sessions
中。 array
- 會話數據存儲在內存中的 PHP 數組中,不會持久保存。 其中一些驅動程序有設置要求。因此,在使用它們之前,務必檢查 Laravel 文檔以了解如何設置它們。
Laravel 使使用會話變得非常簡單。文檔很好地解釋瞭如何與會話交互。但是,讓我們快速回顧一下基礎知識。
對於我們的示例,我們將假設我們正在構建一個跨越多個頁面的分步嚮導。我們將存儲當前步驟以及每個步驟中輸入的數據到會話中。這樣,當用戶完成所有步驟時,我們可以在嚮導結束時讀取所有提交的數據。
為了使示例簡單,我們還將使用 session()
輔助函數。但稍後我們將討論使用 Session
門面或請求類訪問會話數據。
要從會話讀取數據,您可以使用 get
方法,如下所示:
<code>[ '_token' => 'bKmSfoegonZLeIe8B6TWvSm1dKwftKsvcT40xaaW' '_previous' => [ 'url' => 'https://my-app.com/users' ] '_flash' => [ 'old' => [ 'success', ], 'new' => [] ] 'success' => 'User created successfully.' 'current_team_id' => 123 ]</code>
運行上述代碼將返回為 wizard:current_step
鍵存儲在會話中的值。如果該鍵在會話中沒有存儲的值,它將返回 null
。
此方法還允許您定義一個默認值,如果鍵不存在則返回:
<code>$currentStep = session()->get(key: 'wizard:current_step');</code>
運行上述代碼將返回為 wizard:current_step
鍵存儲在會話中的值。如果該鍵在會話中沒有存儲的值,它將返回 1
。
還可能有一些時候您想從會話讀取數據並同時將其刪除(因此無法再次訪問)。您可以為此使用 pull
函數:
<code>$currentStep = session()->get(key: 'wizard:current_step', default: 1);</code>
運行上述代碼將返回為 wizard:current_step
鍵存儲在會話中的值,然後將其從會話中刪除。
要將數據寫入會話,您可以使用 put
函數,如下所示:
<code>$currentStep = session()->pull(key: 'wizard:current_step');</code>
運行上述代碼將把數組(在第二個參數中傳遞)存儲為 wizard:step_one:form_data
鍵的值。
類似地,您還可以使用 push
方法將數據推送到會話中的數組:
<code>session()->put( key: 'wizard:step_one:form_data', value: [ 'name' => 'Ash Allen', 'email' => 'ash@example.com', ], );</code>
假設 wizard:step_one:form_data:languages
鍵具有以下數據:
<code>session()->push( key: 'wizard:step_one:form_data:languages', value: 'javascript', );</code>
上述代碼(調用 push
方法)將更新會話值:
<code>[ '_token' => 'bKmSfoegonZLeIe8B6TWvSm1dKwftKsvcT40xaaW' '_previous' => [ 'url' => 'https://my-app.com/users' ] '_flash' => [ 'old' => [ 'success', ], 'new' => [] ] 'success' => 'User created successfully.' 'current_team_id' => 123 ]</code>
如果 wizard:step_one:form_data:languages
值尚不存在於會話中,則使用 push
將創建會話鍵並將值設置為包含您傳入的值的數組。
Laravel 還提供了一些方便的輔助方法,允許您增加和減少會話中的值:
您可以像這樣增加會話中的值:
<code>$currentStep = session()->get(key: 'wizard:current_step');</code>
當我們運行上面的代碼時,如果 wizard:current_step
會話值為 3,它現在將增加到 4。
您還可以像這樣減少會話中的值:
<code>$currentStep = session()->get(key: 'wizard:current_step', default: 1);</code>
如果值尚不存在於會話中,則將它們視為 0。因此,對空會話值調用 increment
將值設置為 1。對空會話值調用 decrement
將值設置為 -1。
這兩種方法都允許您指定要增加或減少的數量:
<code>$currentStep = session()->pull(key: 'wizard:current_step');</code>
您還可以使用 forget
方法從會話中刪除數據:
<code>session()->put( key: 'wizard:step_one:form_data', value: [ 'name' => 'Ash Allen', 'email' => 'ash@example.com', ], );</code>
運行上述代碼將從會話中刪除屬於 wizard:current_step
鍵的數據。
如果您想一次刪除多個鍵,您可以將鍵數組傳遞給 forget
函數:
<code>session()->push( key: 'wizard:step_one:form_data:languages', value: 'javascript', );</code>
或者,如果您希望從會話中刪除所有數據,您可以使用 flush
函數:
<code>[ `php`, ]</code>
Laravel 還提供了一些方便的輔助函數來檢查會話中是否存在數據。
您可以使用 has
方法檢查會話中是否存在鍵以及其值是否不是 null
:
<code>[ `php`, `javascript`, ]</code>
如果值存在且不是 null
,則上述代碼將返回 true
。如果值為 null
或鍵不存在,它將返回 false
。
類似地,您還可以使用 exists
方法檢查會話中是否存在鍵(無論值是否為 null):
<code>session()->increment(key: 'wizard:current_step');</code>
您還可以檢查會話中是否根本不存在會話:
<code>session()->decrement(key: 'wizard:current_step');</code>
可能有時您想在會話中持久保存一些數據,但僅限於下一個請求。例如,您可能希望在用戶提交表單後向用戶顯示成功通知。
為此,您可以使用 flash
方法:
<code>session()->increment(key: 'wizard:current_step', amount: 2); session()->decrement(key: 'wizard:current_step', amount: 2);</code>
如果您要運行上述代碼,在下一次請求中,您可以從會話中讀取該值(使用類似 session()->get('success')
的內容)進行顯示。然後將其刪除,以便在下一次請求中不可用。
可能有時您有一些閃存數據(在先前的請求中添加),並且您想將其保留到下一個請求。
您可以使用 reflash
方法刷新所有閃存數據:
<code>session()->forget(keys: 'wizard:current_step');</code>
或者,如果您只想保留一些閃存數據,您可以使用 keep
方法:
<code>[ '_token' => 'bKmSfoegonZLeIe8B6TWvSm1dKwftKsvcT40xaaW' '_previous' => [ 'url' => 'https://my-app.com/users' ] '_flash' => [ 'old' => [ 'success', ], 'new' => [] ] 'success' => 'User created successfully.' 'current_team_id' => 123 ]</code>
運行上述代碼將保留 success
和 error
閃存會話值,但會刪除下一個請求的任何其他閃存數據。
到目前為止,我們只在示例中使用了 session()
輔助函數。
但您也可以使用 IlluminateSupportFacadesSession
門面或 IlluminateHttpRequest
類與會話交互。
無論您使用哪種方法,您仍然可以使用本文前面介紹的相同方法。這些方法只是與會話數據交互的不同方式。
要使用 Session
門面,您可以像這樣調用方法:
<code>$currentStep = session()->get(key: 'wizard:current_step');</code>
或者,您可以通過調用注入到控制器方法中的 IlluminateHttpRequest
實例上的 session
方法來訪問會話。假設您有如下控制器方法:
<code>$currentStep = session()->get(key: 'wizard:current_step', default: 1);</code>
每種方法都是完全有效的,因此您可以決定您和您的團隊更喜歡哪一種。
對於較小的項目,使用我們前面討論過的方法與會話交互是完全可以的。但是,隨著 Laravel 項目的增長,您可能會遇到一些問題,這些問題會導致錯誤並使您的代碼更難以維護。
因此,我們現在將介紹一些潛在的陷阱以及如何避免它們。
我看到的一個常見陷阱(我自己也經歷過很多次)是會話鍵中的錯字。
堅持使用我們的嚮導示例,假設我們要將當前步驟存儲在會話中。因此,我們的代碼可能如下所示:
<code>$currentStep = session()->pull(key: 'wizard:current_step');</code>
然後稍後,在代碼庫的不同部分,我們可能想從會話中讀取當前步驟:
<code>session()->put( key: 'wizard:step_one:form_data', value: [ 'name' => 'Ash Allen', 'email' => 'ash@example.com', ], );</code>
您看到我剛才犯的錯誤了嗎?我意外地嘗試讀取 wizard:step
鍵而不是 wizard:current_step
鍵。
這是一個簡單的示例,但在大型代碼庫中,很容易犯這種錯誤。這些顯而易見的錯誤也可能是最難發現的。
因此,避免這些錯字的一種有用方法是使用常量或方法來生成會話鍵。
例如,如果會話鍵是靜態的,您可以定義一個常量(可能在稍後我們將介紹的會話類中),如下所示:
<code>session()->push( key: 'wizard:step_one:form_data:languages', value: 'javascript', );</code>
這意味著我們減少了在代碼庫中使用的原始字符串的數量,這有助於減少錯字的數量。
但是,有時您可能需要動態生成會話鍵。例如,假設我們希望我們的 wizard:current_step
鍵包含一個團隊 ID 字段。我們可以創建一個方法來生成密鑰,如下所示:
<code>[ `php`, ]</code>
正如我們在上面的代碼中看到的,我們正在動態生成會話鍵,以便它可以在不同的方法中使用。例如,如果我們試圖查找 ID 為 1 的團隊的當前步驟,則密鑰將是 wizard:1:current_step
。
我在處理存在一段時間的項目時看到的另一個陷阱是會話鍵衝突。
例如,想像一下,幾年前您構建了一個用於創建新用戶帳戶的嚮導。因此,您可能像這樣存儲會話數據:
<code>[ '_token' => 'bKmSfoegonZLeIe8B6TWvSm1dKwftKsvcT40xaaW' '_previous' => [ 'url' => 'https://my-app.com/users' ] '_flash' => [ 'old' => [ 'success', ], 'new' => [] ] 'success' => 'User created successfully.' 'current_team_id' => 123 ]</code>
現在,您已被分配構建一個也具有嚮導的新功能,並且您完全忘記了舊的嚮導和您使用的命名約定。您可能會意外地為新嚮導使用相同的鍵,導致數據衝突並引入潛在的錯誤。
為了避免這種情況,我喜歡用功能名稱作為會話鍵的前綴。因此,對於保存用於創建新用戶的嚮導數據,我可能有以下鍵:
new_user_wizard:current_step
new_user_wizard:step_one:form_data
new_user_wizard:step_two:form_data
然後在我的用於創建新團隊的新嚮導中,我可能有以下鍵:
new_team_wizard:current_step
new_team_wizard:step_one:form_data
new_team_wizard:step_two:form_data
我們將在本文後面介紹如何在會話類中添加這些前綴。
你能告訴我此會話值中存儲的數據類型是什麼嗎?
<code>$currentStep = session()->get(key: 'wizard:current_step');</code>
如果您猜到它是一個 AppDataTransferObjectsWizardsFormData
實例,那麼您是對的。
說笑歸說笑,我想說明的是,當您從會話中讀取數據時,並不總是立即清楚您正在使用什麼類型的數據。您最終必須查看將數據寫入會話的代碼才能弄清楚它是什麼。這可能會分散注意力且耗時,並可能導致錯誤。
您可以向讀取會話數據的代碼添加註釋或文檔塊。但這只是一個提示。如果註釋沒有保持最新(如果會話數據類型發生更改),那麼它就沒有幫助,並且會增加錯誤的可能性。
我喜歡使用的另一種方法是在方法內部讀取會話數據並向該方法添加返回類型。這樣,您可以確保您正在使用的數據類型正確。它還有助於您的 IDE 和閱讀代碼的人員。
例如,讓我們來看這段代碼:
<code>$currentStep = session()->get(key: 'wizard:current_step', default: 1);</code>
我們現在可以立即看到 stepOneFormData
方法返回一個 AppDataTransferObjectsWizardsFormData
實例。這清楚地說明了我們正在使用的數據類型。然後,我們可以在控制器中像這樣調用此方法:
<code>$currentStep = session()->pull(key: 'wizard:current_step');</code>
正如我們在前面幾節中看到的,在 Laravel 中使用會話時,有一些很容易避免(但很常見)的陷阱。
通過使用“會話類”,可以避免(或至少減少)這些陷阱中的每一個。我喜歡使用會話類來在一個地方封裝與單個功能相關的會話數據處理邏輯。
例如,假設我們有一個用於創建用戶和另一個用於創建團隊的嚮導。我將為這些嚮導中的每一個創建一個會話類:
AppSessionsUsersNewUserWizardSession
AppSessionsTeamsNewTeamWizardSession
通過使用會話類,您可以:
在處理會話數據時使用這種基於類的方法在處理大型 Laravel 項目時為我節省了無數次時間。這是一種簡單的方法,可以產生很大的影響。
在前面的示例中,我已經暗示過使用會話類。但是,讓我們更深入地了解我喜歡如何構建這些類。
假設我們有以下用於新用戶嚮導的會話類。乍一看它可能有點讓人不知所措,但讓我們看看代碼,然後分解它:
<code>[ '_token' => 'bKmSfoegonZLeIe8B6TWvSm1dKwftKsvcT40xaaW' '_previous' => [ 'url' => 'https://my-app.com/users' ] '_flash' => [ 'old' => [ 'success', ], 'new' => [] ] 'success' => 'User created successfully.' 'current_team_id' => 123 ]</code>
在上面的 AppSessionsUsersWizardSession
類中,我們首先定義了接受 IlluminateContractsSessionSession
實例的構造函數。通過這樣做,當我們從服務容器中解析 AppSessionsUsersWizardSession
類時,Laravel 將自動為我們注入會話實例。我將稍後向您展示如何在控制器中執行此操作。
然後,我們定義了 5 個基本的公共方法:
getCurrentStep
- 返迴向導中的當前步驟。如果沒有設置步驟,則默認為 1
。 setCurrentStep
- 設置嚮導中的當前步驟。 setFormDataForStep
- 設置嚮導中給定步驟的表單數據。此方法採用步驟號和 AppDataTransferObjectsWizardsUsersFormData
實例。 getFormDataForStep
- 獲取嚮導中給定步驟的表單數據。此方法採用步驟號並返回 AppDataTransferObjectsWizardsUsersFormData
實例或如果數據不存在則返回 null
。 flush
- 從會話中刪除與嚮導相關的所有數據。如果已完成或取消嚮導,您可能希望調用此方法。 您可能已經註意到所有鍵都在方法內部生成。我喜歡這樣做是為了減少使用的原始字符串的數量(並減少錯字的機會)。這也意味著如果我們想添加另一個訪問特定鍵的方法,我們可以很容易地做到這一點。
使用這些鍵生成方法的額外好處是我們可以輕鬆地向鍵添加前綴以避免衝突。在此示例中,我們通過使用 sessionKey
方法將所有鍵的前綴設置為 new_user_wizard:
。
現在此類已設置,讓我們看看我們如何在控制器中與它交互:
<code>[ '_token' => 'bKmSfoegonZLeIe8B6TWvSm1dKwftKsvcT40xaaW' '_previous' => [ 'url' => 'https://my-app.com/users' ] '_flash' => [ 'old' => [ 'success', ], 'new' => [] ] 'success' => 'User created successfully.' 'current_team_id' => 123 ]</code>
正如我們看到的,在上面的示例中,我們將 AppSessionsUsersWizardSession
類註入到我們的控制器方法中。 Laravel 將自動為我們解析會話實例。
然後,我們可以像使用任何其他類一樣與它交互。
起初,這可能感覺像過度抽象和需要維護更多代碼。但是,隨著項目的增長,類型提示、返回類型、鍵生成方法,甚至方法的命名(使您的操作更具描述性)都非常有用。
就像代碼庫的任何其他部分一樣,您應該確保您擁有會話數據的覆蓋範圍,以確保正在讀取和寫入正確的字段。
使用會話類的一大好處是您可以輕鬆地為類中的每個方法編寫集中的單元樣式測試。
例如,我們可以為 AppSessionsUsersWizardSession
類的 getFormDataForStep
方法編寫一些測試。作為提醒,以下是該方法:
<code>$currentStep = session()->get(key: 'wizard:current_step');</code>
我們可以在這裡測試幾個場景:
AppDataTransferObjectsWizardsUsersFormData
對象。 null
。 我們的測試類可能如下所示:
<code>$currentStep = session()->get(key: 'wizard:current_step', default: 1);</code>
在上面的測試類中,我們有兩個測試涵蓋了我們前面提到的兩種情況。
這些單元樣式測試非常適合確保您的會話類配置正確以讀取和寫入會話數據。但它們並不一定能讓你相信它們在代碼庫的其餘部分被正確使用。例如,您可能正在調用 getFormDataForStep(1)
,而您應該調用 getFormDataForStep(2)
。
出於這個原因,您可能還想考慮在您的功能測試(您通常為控制器編寫的測試)中斷言會話數據。
例如,假設您的控制器中有以下基本方法,它會轉到嚮導中的下一步:
<code>$currentStep = session()->pull(key: 'wizard:current_step');</code>
在上面的方法中,我們首先從會話中讀取當前步驟。然後,我們將當前步驟的表單數據存儲在會話中。最後,我們遞增當前步驟並重定向到嚮導中的下一步。
我們將假設我們的 AppHttpRequestsUsersWizardNextStepRequest
類負責驗證表單數據,並在我們調用 toDto
方法時返回 AppDataTransferObjectsWizardsUsersFormData
實例。
我們還將假設 nextStep
控制器方法可通過對 /users/wizard/next-step
路由(名為 users.wizard.next-step
)的 POST 請求訪問。
我們可能想編寫如下測試以確保表單數據被正確存儲在會話中:
<code>[ '_token' => 'bKmSfoegonZLeIe8B6TWvSm1dKwftKsvcT40xaaW' '_previous' => [ 'url' => 'https://my-app.com/users' ] '_flash' => [ 'old' => [ 'success', ], 'new' => [] ] 'success' => 'User created successfully.' 'current_team_id' => 123 ]</code>
在上面的測試中,我們正在使用一些表單數據向 /users/wizard/next-step
路由發出 POST 請求。您可能會注意到我們正在使用 withSession
。此方法允許我們設置會話數據,以便我們可以斷言它被正確讀取。
然後,我們斷言用戶被重定向到嚮導中的下一步,並且會話中的當前步驟設置為 3
。我們還斷言步驟 2
的表單數據被正確存儲在會話中。
正如我們在測試中看到的,我們還通過兩種方式從會話中讀取:
assertSessionHas
方法檢查會話數據是否正確設置。 session()
輔助函數直接讀取會話數據。 這兩種方法都是有效的,因此您可以決定更喜歡哪一種。我在上面的測試中使用了這兩種方法來向您展示您有多種選擇。
希望本文能幫助您很好地理解會話是什麼以及它們如何在 Laravel 中工作。我還希望它們能為您提供一些關於如何使用基於類的方法與會話數據交互以避免一些常見陷阱的想法。
以上是深入研究拉拉維爾的會議的詳細內容。更多資訊請關注PHP中文網其他相關文章!