“用 PHP 開發健壯的代碼”是關於解決大中型應用程序中的實際問題的系列文章。在本文中,PHP 老手 Amol Hatwar 討論了如何有效地使用變量。他還演示了如何通過使用 PHP 中可變的變量名來構造配置文件解析器,以便簡化腳本配置。 在我的前一篇文章中,我研究了在規劃、設計甚至編寫代碼期間必須考慮的一些因素。在本文中,您將真正接觸到實際代碼,並可以看到實際運行中的一些東西。如果您還沒有看過前一篇文章,那麼最好現在就看一看。 正確處理變量 變量與函數是任何計算機語言必不可少的要素。有了變量,您可以將數據抽象化;有了函數,您可以將幾行代碼抽象化。正如 Bruce Eckel 在他的書籍《C++ 編程思想》中所說的那樣,所有編程語言都提供抽象。匯編語言是對底層機器的小抽象。隨後的許多所謂的命令式語言(如 Fortran、BASIC 和 C)是對匯編語言的抽象。 編程語言提供的抽象的種類和質量直接關系到您所能解決的問題的復雜程度。理解 PHP 如何處理變量和函數,將有助於您有效地使用它們。 名稱裡有什麼? 就象我在前一篇文章中提到的那樣,命名約定和編碼約定是重要的。無論您使用什麼命名約定,請記住要在項目中嚴格遵守它。如果您使用應用得最廣泛的命名約定,那麼您的代碼將被更多的人所接受。 對變量進行命名時,在包括腳本時要特別注意不要覆蓋正在使用的變量。在大型應用程序中,當增加新的功能時,這是常見的錯誤根源。防止這一問題的最佳辦法就是使用前綴。把變量所在模塊的名稱縮寫作為前綴來使用。例如,如果一個處理投票的模塊中有一個保存用戶標識的變量,那麼您可以將該變量命名為 $poll_userID 或 $pollUserID。 理解 PHP 變量 PHP 是解釋型語言。這有許多好處,很快您將學習利用其中的一些。第一個很明顯的好處是:它使您省掉了設計-編碼-編譯-測試周期 — 您在編輯器中編寫的任何代碼都立即可使用。然而,最重要的好處是您不用擔心變量的類型以及如何在內存中管理這些變量。所有分配給腳本的內存在執行完腳本後都由 PHP 自動收回。此外,可以對變量執行許多操作而不必知道變量的類型。清單 1 中的代碼在 PHP 中工作十分正常,但在 C 和 Java 語言中會拋出一大堆錯誤消息: 清單 1. 帶變量的樣本 PHP 代碼 安裝完 PHP 後,如要運行運行代碼,可首先將該代碼保存為一個 .php 文件,再將該文件放置在 Web 服務器上,然後將浏覽器指向該文件。更好的辦法是安裝 PHP 的 CGI 版本。然後,通過在 shell 或命令提示符下輸入以下命令,並用包含您的腳本的文件名替代 script-name,這樣就可以運行該腳本了。 path-to-php/php script-name 該代碼能夠正常工作,因為 PHP 是類型寬松的語言。用通俗易懂的英語,可以不考慮變量類型,可以把字符串賦值給整數,以及毫不費力地用較大的字符串替代較小的字符串。這在象 C 這樣的語言中是不可能的事情。在內部,PHP 將變量所擁有的數據與類型分開存儲。類型存儲在單獨的表中。每當出現包含不同類型的表達式時,PHP 自動確定程序員想要做什麼,接著更改表中的類型,然後自動對表達式求值。 介紹一個常見的小問題 不用擔心類型固然很好,但有時那也會使您陷入真正的麻煩。怎麼回事呢?這裡有一個實際的示例:我常常必須把在基於 Windows 的 PC 上創建的內容移到 Linux 系統,以便能在 Web 上使用它們。基於 Windows 的文件系統在處理文件名時是不區分大小寫的。文件名 DefParser.php 和 defparser.php 指向 Windows 上的同一文件。在 Linux 操作系統上,它們指向不同的文件。您可能提倡文件名要麼全用大寫,要麼全用小寫,但最好的做法應該是使大小寫保持不變。 解決這個小問題 假設您想要一個函數,它能在不考慮大小寫的情況下檢查給定文件是否存在於某個目錄中。首先,將這個任務分解成一些簡單的步驟。分解代碼可能聽起來有些可笑,但它確實有助於您在編寫代碼時將主要精力放在這段代碼上。另外,在紙上重寫步驟始終比重寫代碼容易得多: 獲取源目錄中的所有文件名 過濾掉 . 和 .. 目錄 檢查目標文件是否存在於該目錄中 如果文件存在,則獲取具有正確大小寫的文件名 如果名稱不匹配,則返回 false 要讀取目錄的內容,需要使用 readdir() 函數。可以在 PHP 手冊(請參閱參考資料)中獲取有關該函數的更多細節。至於現在,只要知道:readdir() 在每次調用時會逐個返回給定目錄中所有文件的名稱。在列出了所有的文件名後,它返回 false。您將使用一個循環,該循環在 readdir() 返回 false 時終止。 但這樣就夠了嗎?請記住,PHP 是類型寬松的語言,這意味著會將整型值 0 與 false 視為相同(甚至 C 也把 0 和布爾值 false 視為等價)。問題不是該代碼是否能正常工作;想象一下,如果文件的名稱是 0 會如何!該腳本會過早終止。可以使用以下腳本(清單 2)來確定 0 與布爾值 false 的等價性: 清單 2. 確定 0 與布爾值 false 是否等價的腳本 那麼您可以做什麼呢?您知道 PHP 會在內部存儲類型,而如果能夠訪問這些類型的話,問題就解決了。布爾值 false 和整型值 0 明顯是不同的。 PHP 有一個 gettype() 函數,但讓我們在這裡選擇更簡單的方法。您可以使用 === 運算符(是的,有三個等號)。不同之處在於該運算符同時比較數據的值和類型。如果您對此覺得有些疑惑,PHP 還有 !== 運算符。只有 PHP 4 中才有這些新型運算符和 gettype() 函數。清單 3 顯示了解決該問題的完整代碼: 清單 3. 完整代碼 觀察中得到的經驗 我不打算對清單 3 中各個函數的功能加以說明,相反,我鼓勵您查閱 PHP 手冊(請參閱參考資料)。當您使用不熟悉的函數時,假設的參數與返回值的類型會是另一個錯誤根源。我沒有對 PHP 中的內置函數加以說明,而是打算說明一些不太一目了然的事情。 當終止條件中涉及不同的變量類型時,通過使用 === 和 !== 運算符進行強類型檢查是很重要的。 由各部分組成的代碼 我本來可以將整個腳本編寫為一個函數,但這裡我卻把代碼分割成兩個函數。還記得前一篇文章中的“分而治之”規則嗎?我這麼做正是因為每個函數所起的作用不同。如果您用其它腳本獲取某個目錄的內容,那麼現在就可以使用方便的實現。我希望您考慮一些事情:想象一下將整個腳本作為一個函數來實現,然後想象調試、測試和重用代碼所需的工作。 正確使用循環 現在看看 foreach 循環,想想為什麼不用 for 循環?使用 for 循環要求您知道數組中項的數目 — 需要一個額外的步驟。此外,在處理 PHP 數組時,有可能超出數組邊界。也就是說,在數組只有 10 個元素時,試圖訪問它的第 15 個元素。PHP 的確會給出一個小警告,但據我所知,在一些情況下,當反復運行某個腳本時,CPU 活動率會突然上升到 100% 而服務器性能則連續下降。我建議您盡可能地避免使用 for 循環。 斷言性的 if 最後,我希望您研究一下那個在 get_file_list() 函數中用於忽略 . 和 .. 目錄的較大的 if 條件。顯然,我可以采用傳統的方法,根據常數來檢查變量。但在我自己的許多編碼昏招中,我經常會遺漏等號並且在以後找不到哪裡出了問題。當然,PHP 不會報錯,因為它認為我想進行賦值而不是比較。當您根據變量來比較常數並且又遺漏了一個等號時,PHP 會拋出錯誤消息。 可變的變量名 現在來討論一些奇妙的事情。作為新手的開發人員認為,使用可變變量來完成任務是一種令人費解的方法,所以常常回避它。實際上,很容易理解和使用可變變量。它們已經不止一次地幫我擺脫困境,而且它們是一種重要的語言元素。事實上,在有些情況下,使用可變變量在所難免。很快我將研究一種此類現實情況,但首先讓我們看看可變變量到底是什麼。讓我們先嘗試一下清單 4 中的代碼: 清單 4. 具有可變變量的代碼 首先,清單 4 中的代碼聲明了名為 $myStr 的變量,並將字符串 I 賦給它。接下來的語句定義了另一個變量。但這次,變量的名稱是 $myStr 中的數據。$$myStr 是一種告訴 PHP 產生另一個變量的方法,其意思是“我想要一個變量,可以在變量 $myStr 中找到這個變量的名稱”。當然,為做到這一點,必須定義 $myStr。所以,現在您有一個名為 I 的變量,並用字符串 am 給它賦值。接下來的語句做了同樣的