Fork me on GitHub

歡迎

目前網路上充斥著大量的過時資訊,讓 PHP 新手誤入歧途,並且傳播著錯誤的實踐方法與不安全的代碼。 PHP 之道 是一個快速易讀的指南,包含 PHP 熱門編碼標準、連結網路上權威教程和現時貢獻者認定的最佳實踐方法。

使用 PHP 沒有規範化的方式。本網站主要是協助 PHP 新手在他們沒有發現或者是太晚發現的主題, 或是經驗豐富的專業人士已經積習已久的做法提供一些新想法。 本網站也不會告訴您應該使用什麼樣的工具,而是提供多種選擇的建議,並盡可能地說明方法及用法上的差異。

當有更多有用的資訊以及範例時,此文件會隨著相關技術發展持續更新。

翻譯

PHP 之道 已經(或即將)翻譯成多種語言:

如何貢獻

幫助我們讓本網站作為 PHP 新手的最佳資源!在 GitHub 上貢獻

口耳相傳

您可以在網站上放置 PHP 之道 的廣告橫幅。謝謝您的支持,並讓 PHP 新手能夠知道哪裡可以找到好的資訊!

廣告橫幅

Back to Top

入門指南

使用最新穩定版本 (5.6)

如果你剛開始學習 PHP,請使用最新的穩定版本 PHP 5.6。PHP 近年來有巨大的改進,增加了許多強大的 新特性。雖然 5.2 和 5.6 之間增加的版本號似乎很小,但他代表了 重大的 改進。如果你想查詢一個函數的使用方法,在 php.net 官方網站的 PHP 手冊上查詢。

內建網站伺服器

PHP 5.4 之後,你可以開始學習 PHP 而不需要安裝與設定一個完整的網頁伺服器。要啟動內建伺服器,從終端機進入專案的 Web 根目錄下,執行下面的指令:

> php -S localhost:8000

Mac 安裝

OSX 會預載 PHP 在系統中,但版本略舊於最新的穩定版本。Lion 預載 PHP 5.3.6,Mountain Lion 是 5.3.10,而 Mavericks 則是 5.4.17,Yosemite 則是 5.5.9,但在 PHP 5.6 出來之後,這些往往是不夠的。

這裏有許多方式可以在 OS X 上安裝 PHP。

透過 Homebrew 安裝 PHP

Homebrew 是一個強大的 OS X 專用套件管理系統,它可以讓您輕鬆地安裝 PHP 和各種擴充套件(extensions)。

Homebrew PHP 是一個包含與PHP相關的Formulae,能讓您透過 homebrew 安裝PHP。也就是說,你可以透過 brew install指令,安裝 php53php54php55、或 php56,並且透過修改你的路徑 PATH 變數去切換各種版本。

透過 phpbrew 安裝 PHP

phpbrew 是一個專門安裝與管理多重 PHP 版本的工具。它在應用程式或專案 PHP 版本需求不同的情況下會非常有用,讓你不再需要使用虛擬機器處理這種情況。

編譯原始碼

另一個可以讓你控制你要安裝的PHP版本的選擇就是自行編譯。在這種方法,您必須先確認您是否已經透過「Apple Developer: Mac Dev Center」下載、安裝 Xcode 或是 “Command Line Tools for XCode”

整合包 (All-in-One Installers)

上述所列的解決方式主要是針對 PHP 本身,並且不包含:像是 Apach 、 Nginx 、或是 SQL 伺服器。整合包像是 MAMPXAMPP 會幫你安裝這些軟體並且將他們綁在一起,但是易於安裝的背後就是犧牲了一些彈性。

Windows 安裝

在 Windows 下有多種安裝 PHP 的方式,你可以 下載安裝檔 並使用 .msi 的安裝檔。從 PHP 5.3.0 之後,該安裝檔將不再提供下載與支援。

如果是為了學習或者是本地端開發需求,從 PHP 5.4 之後,你可以使用內建的網頁伺服器,省去設定伺服器的麻煩。如果你想要也包含網頁伺服器以及 MySQL 的一鍵安裝包,那像是 網頁平台安裝包 的工具,如 Zend Server CEXAMPPEasyPHPWAMP 將會幫助您快速建立 Windows 開發環境。不過這些工具將會與正式執行環境會有些許差別,如果你是在 Windows 開發,而部署至 Linux 上的情況下,請小心。

如果你希望將網頁部署到 Windows 上,那 IIS7 將會提供最穩定且最佳的性能。你可以使用 「phpmanager]phpmanager (IIS7 圖形化插件) 能讓你簡單設定並管理 PHP。IIS7 也有內建 FastCGI,你僅需將 PHP 設定為他的處理器即可。詳情請見 dedicated area on iis.net

Vagrant

如果你在開發應用與發布應用時使用不同的環境,那麼在正式上線時,可能會發生一些奇怪的錯誤。 在與一群團隊的開發者共同開發時,要保持每個環境同步,且所有的函式庫皆在同版本上,是最棘手的一件事情。

如果你是在 Windows 上開發,部署到 Linux(或非 Windows 平台),或者是團隊裡工作,你應該考慮使用虛擬機器。聽起來很麻煩,但使用 Vagrant 你僅需幾個步驟就能設定好一個簡單的虛擬機器。再來需要手動設定基礎環境,或者你可以使用 “部署軟體” 如 PuppetChef 來幫你完成這些事情。部署基礎環境是很好的做法保證大家的開發環境以相同的方式建立。並且省去你維護那些複雜的”安裝指令”。你也可以輕易地毀掉現有的基礎環境後再重新建立一個新的,這樣你就可以有一個全新的環境。

Vagrant 會建立共用資料夾,讓你可以在你的主機與虛擬機之間共用程式碼,也就是你可以在你的主機上建立與編輯檔案,然後在你的虛擬機器上面執行。

小助手

如果你想要取得一些 Vagrant 上的使用幫助的話,可以參考下面三個服務:

Back to Top

程式碼風格指南

PHP 社群百花齊放,擁有大量的函式庫、框架和元件。PHP 開發者通常會選擇多個外部套件並將它們整合進單一專案中。因此 PHP 程式碼遵循(盡可能接近)一個共通的程式碼風格就會非常重要,這讓開發者可以輕鬆地將多個外部套件整合在自己的專案當中。

Framework Interop Group 提出並通過了一系列的風格建議。其中有部分是關於程式碼風格的,即 PSR-0PSR-1PSR-2PSR-4。這些建議是由一系列專案像是 Drupal, Zend, Symfony, CakePHP, phpBB, AWS SDK, FuelPHP, Lithium 所採用。你可以在您的專案中使用或者是繼續使用您自己的風格。

理想狀況下,你應該遵循一個已知的標準來撰寫 PHP 程式碼。可能是 PSR 的組合或者是 PEAR 或 Zend 程式碼標準的其中一個。這代表其他開發者能夠方便的閱讀與使用你的程式碼,且使用這些套件的應用程式能夠平順地與許多第三方套件一起使用。

你可以使用 PHP_CodeSniffer 來檢查程式碼是否符合這些準則,而文字編輯器 Sublime Text 2 的套件也可以提供即時檢查。

使用 Fabien Potencier 的 PHP Codeing Standards Fixer 可以自動修正你的程式碼語法,讓其符合標準,不用你自己手動修復。

所有的變數名稱和程式碼結構建議使用英文編寫。註解可以使用任何語言,只要讓現在及未來的夥伴能夠容易閱讀即可。

Back to Top

語法高亮

編程範式

PHP 是一個彈性的動態語言,支援多種編程技術。這幾年一直不斷的進化,重要的里程碑包含 PHP 5.0 (2004) 增加完善的物件導向模型,PHP 5.3 (2009) 增加匿名函式與命名空間以及 PHP 5.4 (2012) 增加的 traits。

物件導向編程

PHP 擁有完整的物件導向編程的特性,包含支援類別、抽象類別、介面、繼承、構造函數、克隆和異常等。

函式編程

PHP 支援第一類函式(first-class function),即函式可以被賦值給一個變數,包括使用者自定義或者是內建函式,然後動態調用它。函式可以作為參數傳遞給其他函式(稱作高階函數),也可以做完函數返回值返回。

PHP 支援遞迴,也就是函式可以呼叫自己,但多數 PHP 程式碼較常使用迭代。

自從 PHP 5.3 (2009) 之後開始引入支援閉包匿名函式。

PHP 5.4 支援將閉包綁定到物件的作用域中,並改善其可調用性,如此即可在大部分狀況下使用匿名函式取代一般函式。

元編程

PHP 通過反射 API 和魔術方法機制,支援多種方式的元編程。開發者通過魔術方法,如 __get(), __set(), __clone(), __toString(), __invoke(), 等等,可以改變類別的行為。Ruby 開發者經常說 PHP 沒有 method_missing 方法,實際上通過 __call()__callStatic() 就可以完成相同的功能。

命名空間

如前所述,PHP 社群裡許多開發者已經開發了大量的程式碼。這意味著一個函式庫的的 PHP 程式碼可能使用了另外一個函式庫中相同的類別名稱。如果他們使用同一個命名空間,那將會產生衝突導致異常。

命名空間 解決了這個問題。如 PHP 手冊裡所描述,命名空間類似作業系統中的目錄,兩個同名的文件可以共存於不同的目錄下。同理兩個同名的 PHP 類別可以在不同的 PHP 命名空間下共存,就這麼簡單。

因此把你的程式碼放在你的命名空間下就非常重要,避免其他開發者擔心與第三方函式庫衝突。

PSR-0 提供了命名空間的推薦使用方式,它提供一個標準的文件、類別和命名空間的使用慣例,進而讓程式碼做到隨插即用。

2013 年 12 月,PHP-FIG 發布了新的自動加載標準:PSR-4,期望將來替換 PSR-0 標準。但 PSR-4 要求 PHP 5.3 以上的版本,而許多專案都還是使用 PHP 5.2,所以目前兩個都能使用。如果你在新應用或套件中使用自動加載標準,應優先考慮使用 PSR-4。

標準 PHP 函式庫

標準 PHP 函式庫(SPL)隨著 PHP 一起發佈,提供了一組類別和介面。包含了常用的資料結構類別(堆疊、佇列、堆積等等),以及遊走這些資料結構的迭代器,或者你可以自己實作 SPL 介面。

命令列介面

PHP 主要是用來開發網頁應用程式,但用來開發命令列介面(CLI)程式也很好用。命令列程式可以幫你完成自動化的任務,如測試、部署和應用管理。

CLI 程式很強大,因為你可以直接使用應用的程式碼而不需要為它建立網頁圖形化介面。唯一要注意的是,請不要將你的 CLI 程式放置在公開的網頁根目錄下。

試著在命令列下執行 PHP:

> php -i

選項 -i 會像 phpinfo 函式一樣將你的 PHP 設定值顯示出來。

選項 -a 提供一個互動式介面,類似 ruby 的 IRB 或者是 python 的互動式介面。此外還有其他有用的 命令列選項

接下來,來寫一個簡單的 “Hello, $name” CLI 程式。首先建議一個名為 hello.php 的檔案如下。

<?php
if ($argc != 2) {
    echo "Usage: php hello.php [name].\n";
    exit(1);
}
$name = $argv[1];
echo "Hello, $name\n";

PHP 會在程式執行時根據參數建立兩個特別的變數,$argc 是一個整數,代表參數的 個數$argv 則是一個陣列變數包含了每個參數的 。而第一個變數就是你的程式檔名,如此例中即是 hello.php

命令執行失敗時,可以使用 exit() 表達式來返回一個非零整數來告知 Shell。常用的 exit 返回值可以查看 列表

執行上述的程式,在命令列輸入:

> php hello.php
Usage: php hello.php [name]
> php hello.php world
Hello, world

XDebug

除錯器是軟體開發過程中一個相當有用的工具。透過它可以追蹤程式碼執行過程、查看堆疊資訊。XDebug 是一個 PHP 除錯器,能夠整合在多款 IDE 內,提供中斷點和查看堆疊訊息等功能,還可以和 PHPUnit 、 KCacheGrind 這類工具配合,提供計算程式碼涵蓋率與程式碼分析。 單單依靠 var_dump/print_r 來除錯的你, 是否經常處在困境中,除錯器將會是你的最佳救星,趕緊來試試看吧。

安裝 XDebug 有些麻煩,但其中一個重要功能”遠端除錯” ——如果你是在本機端進行開發,並在虛擬機器或其他主機上進行測試,那麼”遠端除錯”這個功能將對你非常有用。

通常,你需要修改 Apache VHost 或 .htaccess 檔案,加入以下設定:

php_value xdebug.remote_host=192.168.?.?
php_value xdebug.remote_port=9000

“remote host” 和 “remote port” 將會對應本機位置與 IDE 監聽埠 ,同時 IDE 開啟等待連接模式,並拜訪此網址:

http://your-website.example.com/index.php?XDEBUG_SESSION_START=1

如此 IDE 就會監控腳本的執行,讓你可以設定中斷點和查看變數值。

圖形化的除錯器讓逐步檢視程式碼、查看變數與即時監看變得十分容易。許多 IDE 內建或是提供套件安裝 xdebug 除錯器。例如 MacGDBp 是一套 Mac 上免費、開源的軟體。

Back to Top

相依管理

PHP 有許多的函式庫、框架及元件可以給你選擇。你的專案將會有可能使用到它們 — 這就是專案套件相依管理 一直到最近,PHP並沒有一個好的方法來做專案的套件相依性管理,即使你手動的管理它們,你還是需要擔心自動讀取器。

現在PHP有二個主要的套件管理系統 - Composer 及 PEAR. 哪一個比較適合你? 答案是二者皆是

一般來說,Composer 的套件只能在你的專案之中可以使用,反之一個 PEAR 的套件可以在你所有的 PHP 專案中使用。雖然 PEAR 可能聽起來比較簡單使用。但他們二者各有優點在不同的專案之中幫助你使用套件。

Composer 與 Packagist

Composer 是一個傑出的PHP套件管理程式。在 composer.json 檔案中寫入你專案所要使用的套件在打上一點簡單的指令,Composer 將會自動幫你下載並設定你專案中所需要的套件。

現在已經有許多 PHP 函式庫相容於 Composer,並且隨時都可以在你的專案中使用.這些”packages(套件)”都會列在 Packagist,這是一個官方的 Composer 相容的套件倉庫。

如何安裝 Composer

你可以區域性的安裝 Composer (在你目前工作的目錄;這裡不是很推薦)或是全域(e.g. /usr/local/bin).我們假設你想安裝區域的 Composer.從你專案的根目錄輸入:

curl -s https://getcomposer.org/installer | php

這串指令將會下載 composer.phar (一個PHP執行檔)。你可以使用 php 執行這個檔案用來管理你的專案套件。 註記:假如你是直接下載程式來編譯,請先確認你下載編譯的檔案是否安全

Windows環境下安裝

對於Windows的使用者而言最簡單取得及執行的方法就是使用 ComposerSetup 安裝檔,安裝檔會提供全域性的 composer 及設定你的 $PATH,所以你可以直接在命令視窗中的任何目錄下使用 composer

如何手動安裝 Composer

要手動安裝Composer是一件較進階的技術;僅管如此還是有許多開發者有各種原因喜歡使用這種互動式的應用程式安裝 Composer。在安裝前請先確認你的PHP安裝項目如下:

由於手動安裝沒有做這些檢查,你必須自已衡量決定是否值得做這些事,以下是如何取得 Composer 手動安裝:

curl -s https://getcomposer.org/composer.phar -o $HOME/local/bin/composer
chmod +x $HOME/local/bin/composer

路徑 $HOME/local/bin (或是你選擇的路徑) 應該在你的 $PATH 環境變數中。這將會影響 composer 這個指令是否有效.

當你已經安裝到文件叫你執行 Composer 指令是 php composer.phar install時,你可以使用下列指令替代:

composer install

本章節我們假設你已經全域性的安裝 Composer。

如何設定及安裝獨立套件

Composer 會持續的追縱你專案的獨立套件透過一個叫 composer.json 的檔案。 如果你希望你可以自已管理這個檔案。或是使用Composer它他自已會管理。composer require 這個指令會新增一個專案獨立套件進去且如果你沒有 composer.json 這個檔案他會直接建立。這裡有個範例是為你的專案加入 Twig這個獨立套件。

composer require twig/twig:~1.8

另外 composer init 這個指令將會指引你建立一個完整的 composer.json 檔案在你的專案之中。無論你使用哪種方式當你已經建立你的 composer.json 檔案,你可以告訴 Composer 去下載及安裝你的獨立套件進 vendors/ 目錄中。這這指令也適用於所有你已經下載並已經提供一個 composer.json 的專案:

composer install

接下來,新增這一行到你的應用程式中主要 PHP 檔案,這將會告訴 PHP 使用 Composer 的自動讀取器並在你的專案中使用獨立套件。

<?php
require 'vendor/autoload.php';

現在你可以使用你專案中的獨立套件且它們會自動完成讀取的動作。

更新獨立套件

Composer 會建立一個檔案叫 composer.lock 並存放每個套件實際下載的版本編號當你第一次執行 php composer.phar install 時。假如你要分享你的專案給其他開發者。當 composer.lock 這個檔案也在你分享的檔案之中的話。 當別的開發者執行 php composer.phar install 這個指令時他們將會得到與你相同一樣的版本套件。 當你要更新你的獨立套件時請執行 php composer.phar update

這是最有用且當你需要靈活的定義你所需要的套件版本. 舉例來說一個版本定義為 ~1.8 時他的意思為任何比 1.8.0 新的版本,但小於 2.0.x-dev”。你也可以使用 * 這個符號在 1.8.* 之中。現在Composer在更新時將會升級你的獨立套件至最新符合你所限制的版本。

更新通知

要接收關於新版本的更新通知。你可以註冊 VersionEye,這是一個網頁服務可以監控你的 Github 及 BitBucket 帳號中的 composer.json 檔案並且當有新套件更新時會寄 email 給你。

確認你的獨立套件資安議題

Security Advisories Checker 是一個網頁服務及一個指令工具二者都會檢查你的 composer.lock 檔案且告訴你假如你需要更新任何獨立套件。

PEAR

另一個舊有套件管理工具也是被許多 PHP 開發者喜愛的就是 PEAR。它有許多行為與 Composer 相同,但卻有顯著的不同。

PEAR 必需準備幾個套件有一個特定的結構,這也意味著套件的作者必須準備好這些東西來使用 PEAR。你不可能不準備這些東西

PEAR 安裝許多套件都是全域性的,這意味著當你結束安裝後這些套件可以適用於伺服器上所有的專案. 這對許多專案而言都是不錯的如果他們都需買依頼有著同樣的版本及相同的套件。但可能出現的問題是當有二個專案出現版本衝突時。

如何安裝 PEAR

你可以下載 phar 的安裝器來安裝 PEAR 並且執行它。PEAR 的文件有寫著詳細的方式 install instructions

假如果使用 Linux,你也可以看一下屬於 Debian 及 Ubuntu 的套件佈署管理器。舉例來說 apt 就有一個 php-pear 的套件可安裝。

如何安裝套件

假如套件已經列在 PEAR packages list上了,你可安裝它使用指定的官方名稱:

pear install foo

假如套件被其他的頻道擁有,你需要先 discover 這個頻道並且當安全時指定它 Using channel docs有更多關於這個主題的資訊你可以去看看。

用 Composer 掌管 PEAR 的獨立套件

如果你已經使用 Composer 且你也想要安裝一些 PEAR 的程式。你可以使用 Composer 來掌握你的 PEAR 套件。下面的範例將會安裝 PEAR 程式來自 pear2.php.net:

{
    "repositories": [
        {
            "type": "pear"
            "url": "http://pear2.php.net"
        }
    ]
    "require": {
        "pear-pear2/PEAR2_Text_Markdown": "*"
        "pear-pear2/PEAR2_HTTP_Request": "*"
    }
}

在第一個 "repositories" 區塊將會到讓 Composer 知道這應該是一個 “initialise” (或 “discover” PEAR 的術語) 的 PEAR 倉庫。然後 require 的區塊將會前綴這個套件名稱像下面這樣:

pear-channel/Package

“pear” 前綴是一件有點笨的寫程式方式來避免任何的版本衝突,因為一個 pear 的頻道可以允許有一樣的另外的其他套件名稱。舉例來說頻道的縮寫(或是完整網址)可以用來參考這個套件是屬於哪個頻道的。

當安裝這個程式碼後這將會安裝至你的 vendor 目錄並且透過 Composer 自動讀取器自動可以使用。

vendor/pear-pear2.php.net/PEAR2_HTTP_Request/pear2/HTTP/Request.php

要使用這個 PEAR 套件可以簡單的參考下面方式:

$request = new pear2\HTTP\Request();

Back to Top

開發實踐

基礎知識

PHP 是一種兼容並蓄的語言,讓各種程度的開發者都能快速且有效率的進行開發。然而,當我們在這個語言不斷向上成長的同時,往往會有個壞習慣抄捷徑(或略過)那些基礎,為了解決這個問題,這個章節用來重新檢視 PHP 的基礎開發實踐。

日期與時間

PHP 提供 DateTime 類別處理讀取、設定、比較和計算日期與時間。儘管 PHP 有許多日期與時間相關的函數,但是 DateTime 類別提供更佳的物件導向介面來處理常見的操作。並且能處理時區問題(礙於篇幅這裡並不多著墨)。

要使用 DateTime 類,可以使用工廠模式方法 createFromFormat() 來把原始的日期時間字串轉換成時間物件,或直接用 new \DateTime 取得當下日期時間的 DateTime 物件。可用 format() 方法轉換 DateTime 物件為字串作為輸出。

<?php
$raw = '22. 11. 1968';
$start = \DateTime::createFromFormat('d. m. Y', $raw);

echo 'Start date: ' . $start->format('m/d/Y') . "\n";

DateTime 計算時間通常需要 DateInterval 類別,如 add()sub() 方法,都是將時間差當作參數,而避免使用時間戳記來計算時間區間,這是因為當遇到日光節約或是時區問題都會造成錯誤。並且應該盡量使用 diff() 方法計算日期差,這會回傳一個新的 DateInterval 物件,非常容易作為輸出顯示使用。

<?php
// create a copy of $start and add one month and 6 days
$end = clone $start;
$end->add(new \DateInterval('P1M6D'));

$diff = $end->diff($start);
echo 'Difference: ' . $diff->format('%m month, %d days (total: %a days)') . "\n";
// Difference: 1 month, 6 days (total: 37 days)

DateTime 物件之間可以直接比較:

<?php
if ($start < $end) {
    echo "Start is before end!\n";
}

最後一個範例來展示 DatePeriod 類,它用來迭代處理週期性的事件,傳入開始時間、結束時間以及時間區間,將會回傳此區間內的所有事件。

<?php
// output all thursdays between $start and $end
$periodInterval = \DateInterval::createFromDateString('first thursday');
$periodIterator = new \DatePeriod($start, $periodInterval, $end, \DatePeriod::EXCLUDE_START_DATE);
foreach ($periodIterator as $date) {
    // output each date in the period
    echo $date->format('m/d/Y') . ' ';
}

設計模式

在程式架構中使用常見的設計模式是很有用的,可以增加程式碼的易維護性,也讓其他開發者更容易理解這些程式碼。

如果你的專案有使用框架,那麼在程式碼與架構上都會遵循框架的規範,自然也繼承了框架中的各種模式。然而基於此之上你仍然可以挑選適合的模式來撰寫程式碼。反之,若沒有使用框架,那麼你必須挑選適合的類型與符合當下規模的較佳模式。

處理 UTF-8

此章節由 Alex Cabal 撰寫,節錄自 PHP Best Practices 並作為我們 UTF-8 建議的基礎

這不是開玩笑的,請小心與細心並前後一致地處理它。

PHP 至今在底層仍未支援 Unicode。而有許多方式可以確認 UTF-8 字串的處理是正確的,但通常不容易,還需要從上而下翻遍程序所有階層,從 HTML、SQL 到 PHP。我們將會聚焦在簡短的實踐總結。

UTF-8 在 PHP 階層

基本的字串操作,像是連接兩個字串、賦值給變數,並不需要特別作 UTF-8 處理。然而,大部分字串函數,像是 strpos()strlen() 則確實需要考慮。這些函數通常以 mb_* 作為開頭:像是 mb_strpos()mb_strlen()。這類函數藉由Multibyte String Extension設計用來專門用來處理 Unicode 字串。

每次操作 Unicode 字串,都必須使用 mb_* 這類函數。例如,若你對 UTF-8 字串使用 substr() 有很大的機會結果會是包含半字元的亂碼,正確的方式是使用 mb_substr()

困難的是,要記得每次都使用 mb_* 函數,萬一有個地方沒有用到,那麼你的 Unicode 字串就有可能在接下來的程序中被轉變成亂碼。 並不是所有的字串函數都有對應的 mb_* 函數,就算只有一處沒有正確轉換,都有可能會出現亂碼。

你應該在 PHP 程式的開頭使用 mb_internal_encoding() (或是在最上層的全域載入的地方使用),若是有輸出訊息到瀏覽器,則要再加上 mb_http_output() 函數。明確定義你的字串編碼可以幫你省下許多頭痛的時間。

此外,許多 PHP 字串函數都允許設定字元編碼,你應該總是設定成 UTF-8,例如,htmlentities() 函數。而在 PHP 5.4.0 之後已經將 UTF-8 作為 htmlentities()htmlspecialchars() 預設編碼。

最後,如果你建立的是分散式架構程式,且不能確定 mbstring 函數是啟用狀態,那麼則建議使用 patchwork/utf8 這個 Composer 套件。它將會自動判斷該環境能否使用 mbstring 函數。

UTF-8 在資料庫階層

如果你是使用 PHP 來存取 MySQL,有可能會發生使用非 UTF-8 編碼來儲存資料,即使你有遵照上述的注意事項。

為了確保字串從 PHP 到 MySQL 都是 UTF-8 編碼,請確認資料庫和資料表都是設定為 utf8mb4 字元集,且使用 utf8mb4 作為 PDO 連線字串,請見以下範例,這將是非常重要的。

請注意,你必須使用 utf8mb4 字元集來作為完整的支援 UTF-8,而非使用 utf8 字元集!原因請參見瞭解更多。

UTF-8 在瀏覽器階層

使用 mb_http_output() 函數來確保 PHP 輸出 UTF-8 字串給瀏覽器。

瀏覽器需要藉由 HTTP 回應來得知此頁面要被解析成 UTF-8 編碼,藉由歷史的方法來達到需要包含字元集 <meta> 標籤 在頁面的 <head> 標籤內。這種方式是非常有效的,但是設定這個字元集到 Content-Type 標頭實際上會更快

<?php
// 告訴 PHP 此頁面從頭到尾使用 UTF-8 字串
mb_internal_encoding('UTF-8');

// 告訴 PHP 此頁面輸出 UTF-8 字串到瀏覽器
mb_http_output('UTF-8');

// 我們的 UTF-8 測試字串
$string = 'Êl síla erin lû e-govaned vîn.';

// 使用多位元字串函數來進行轉換
// 注意我們如何裁剪非 Ascii 字符來作為演示
$string = mb_substr($string, 0, 15);

// 連接資料庫來儲存轉換字串
// 參見文件的 PDO 範例,以獲得更多的資訊
// 注意 `set names utf8mb4` 指令!
$link = new \PDO(
                    'mysql:host=your-hostname;dbname=your-db;charset=utf8mb4',
                    'your-username',
                    'your-password',
                    array(
                        \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
                        \PDO::ATTR_PERSISTENT => false
                    )
                );

// 資料庫以 UTF-8 編碼儲存我們轉換的字串
// 你的資料庫和資料表是設定成 utf8mb4 字元集,對吧?
$handle = $link->prepare('insert into ElvishSentences (Id, Body) values (?, ?)');
$handle->bindValue(1, 1, PDO::PARAM_INT);
$handle->bindValue(2, $string);
$handle->execute();

// 取得我們剛才存入的字串,來驗證儲存正確。
$handle = $link->prepare('select * from ElvishSentences where Id = ?');
$handle->bindValue(1, 1, PDO::PARAM_INT);
$handle->execute();

// 將結果存入物件,待會要輸出到 HTML
$result = $handle->fetchAll(\PDO::FETCH_OBJ);

header('Content-Type: text/html; charset=UTF-8');
?><!doctype html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>UTF-8 test page</title>
    </head>
    <body>
        <?php
        foreach($result as $row){
            print($row->Body);  // This should correctly output our transformed UTF-8 string to the browser
        }
        ?>
    </body>
</html>

瞭解更多

Back to Top

依賴注入

出自 維基百科

依賴注入是一個讓我們可以移除寫死的相依關係, 並在執行期間或編譯期間改變相依關係的軟體設計模式

這句話讓這個概念聽起來比實際上來的要複雜許多。 依賴注入是透過建構式注入、方法呼叫或設定屬性的其中任何一個來提供元件跟它的相依關係。 就是這麼簡單。

基本概念

我們可以用一個簡單而樸實的例子來說明這個概念。

這裡我們有一個 Database 類別它需要個配接器來跟資料庫溝通。 我們在建構式實例化配接器並建立寫死的相依關係。 這使得測試困難並意味 Database 類別跟配接器有非常緊密地耦合。

<?php
namespace Database;

class Database
{
    protected $adapter;

    public function __construct()
    {
        $this->adapter = new MySqlAdapter;
    }
}

class MysqlAdapter {}

這段程式碼可以用依賴注入重構並因此鬆開了相依關係。

<?php
namespace Database;

class Database
{
    protected $adapter;

    public function __construct(MySqlAdapter $adapter)
    {
        $this->adapter = $adapter;
    }
}

class MysqlAdapter {}

現在我們給 Database 類別它的相依關係而不是它自己建立相依關係。 我們可以建立 接受一個相依關係參數的方法並用這個方法設定它,或者 $adapter 屬性是 public 我們可以直接設定它。

複雜的問題

如果你曾經讀過依賴注入,那你可能已經看過 “控制反轉”“依賴反轉原則” 這詞彙。 這些都是依賴注入解決的複雜問題。

控制反轉

控制反轉就像字面上說的,藉由維持組織的控制從我們的物件完全地分離來「反向控制」一個系統。 從依賴注入的觀點看,這意味藉由在系統別處控制和實例化它們,鬆開相依關係。

多年來,PHP 框架已經實現了控制反轉,然而,問題變成,你要反向控制哪個部分、在哪控制? 舉個例子,MVC 框架通常會提供一個超級物件或基底控制器,其他控制器必須繼承它以取用它的相依關係。 這 就是 控制反轉,然而,這個方法簡單地搬移它們,而不是鬆開相依關係。

依賴注入讓我們藉由在我們需要的時候只注入我們需要的相依關係,更優雅地解決這個問題, 完全不需要任何寫死的相依關係。

依賴反轉原則

依賴反轉原則是物件導向設計原則的 S.O.L.I.D 集合中的「D」,它陳述應該 “依賴於抽象。 而不要依賴於實例”。 簡單的說,這意味著我們的相依關係應該是介面/契約或抽象類別而不是具體的實作。 我們可以簡單地重構上面的例子來遵循這個原則。

<?php
namespace Database;

class Database
{
    protected $adapter;

    public function __construct(AdapterInterface $adapter)
    {
        $this->adapter = $adapter;
    }
}

interface AdapterInterface {}

class MysqlAdapter implements AdapterInterface {}

Database 類別現在依賴於介面而不是實例有幾個好處。

試想,你在一個團隊中工作並且配接器是由同事動工。 在我們的第一個例子中,在可以適當地在單元測試中模擬它之前,我們必須等待同事完成配接器。 現在,相依關係是個 介面/契約,因為我們知道同事將會基於那個契約來做配接器,我們可以快樂地模擬那個介面。

這個方法有一個更大的好處是我們的程式碼現在變成更可擴展的。 如果一年下來我們終究決定想要遷移到一個不同種類的資料庫,我們可以寫一個實作原本介面的配接器並注入它來取代, 當我們可以確保配接器藉由介面遵守契約規定,就不再需要一直重構了。

容器

第一件你應該了解有關依賴注入容器的事就是它跟依賴注入是不同的東西。 容器是個便利的工具能幫助我們實作依賴注入,然而,它們經常被濫用來實作一個反模式:服務定位。 注入一個依賴注入容器作為服務定位器到你的類別中,可以說是建立了比你替換的相依關係更死的相依關係在容器中。 這也讓你的程式碼更不透明並最後更難測試。

大部份現代框架都有它們自己的依賴注入容器,讓你透過設定去繫結你的相依關係。 這意味著實際上你可以寫乾淨且去耦合的程式碼,如同應用程式使用的框架。

延伸閱讀

Back to Top

資料庫

你的PHP程式碼常常需要使用資料庫來保存資訊,這時,你有一些選擇來連接並與你的資料庫互動,在PHP 5.1.0之前的推薦方案是使用一些原生的驅動程式,例如mysqli, pgsql, mssql等等。

如果你在應用程式中只使用 一個 資料庫,原生的驅動程式(native driver)是一個很好的選擇。但是,假如你使用了MySQL和一點點的MSSQL,或是你需要連接到Oracle資料庫,那麼你就不能只使用同一個驅動程式,你將需要針對每個不同的資料庫學習API — 而這些行為非常的愚蠢。

MySQL 擴充套件

PHP的mysql擴充套件已經不再主動的開發,官方也在PHP 5.5.0時不贊成使用,意思是mysql擴充套件將在接下來的幾個釋出版本移除。如果你正在使用類似mysql_connect()mysql_query()這些mysql_*開頭的函式,這些函式將在未來的幾個版本不能使用了。這代表你將需要重寫程式,所以最好的選擇就是開始使用 mysqli 或是 PDO

如果你正要開始寫程式,那麼絕對不要使用 mysql 擴充套件: 請轉而使用MySQLi extension或是使用PDO

PDO 擴充套件

PDO 是從 — PHP 5.1.0 — 可以開始使用的一個和資料庫連結的抽象程式庫,它提供了一個共同的介面來和不同的資料庫溝通。例如,你可以使用同一套程式碼和MySQL或是SQLite溝通。

// PDO + MySQL
$pdo = new PDO('mysql:host=example.com;dbname=database', 'user', 'password');
$statement = $pdo->query("SELECT some\_field FROM some\_table");
$row = $statement->fetch(PDO::FETCH_ASSOC);
echo htmlentities($row['some_field']);

// PDO + SQLite
$pdo = new PDO('sqlite:/path/db/foo.sqlite');
$statement = $pdo->query("SELECT some\_field FROM some\_table");
$row = $statement->fetch(PDO::FETCH_ASSOC);
echo htmlentities($row['some_field']);

PDO不會轉譯你的SQL請求,或是模擬失去的特性。它只是簡單的使用同一個API去連向各個不同類型的資料庫。最重要的是, PDO 允許你簡單的直接將外部的輸入插進你的SQL請求,而不需要擔心SQL注入攻擊(SQL injection attacks)。讓我們假設一個PHP腳本接收一個數字ID當作請求參數。這個ID是用來從資料庫取得使用者的資料。這是錯誤的使用方式。

<?php
$pdo = new PDO('sqlite:/path/db/users.db');
$pdo->query("SELECT name FROM users WHERE id = " . $_GET['id']); // <-- !

這是一個很糟糕的程式碼,你正在插入一個未處理過的請求參數到資料庫請求中。這會導致你在一瞬間被駭入。用一個實際的案例來解釋這個情況:資料庫注入攻擊。想像一下有一個駭客使用一個網址類似這樣http://domain.com/?id=1%3BDELETE+FROM+users。這將會造成 $_GET['id'] 變數的值變成 1;DELETE FROM users ,這會刪除所有你的使用者!因此你應該要使用PDO bound parameters對這個ID輸入消毒。

<?php
$pdo = new PDO('sqlite:/path/db/users.db');
$stmt = $pdo->prepare('SELECT name FROM users WHERE id = :id');
$stmt->bindParam(':id', $_GET['id'], PDO::PARAM_INT); // <-- PDO自動消毒這個變數
$stmt->execute();

這個是正確的程式碼,它在PDO敘述上使用了bound parameter。這些動作在被放入資料庫請求之前逃脫了外部的ID輸入以避免潛藏的資料庫注入攻擊。

你應該要注意到資料庫連線沒有自行關閉而耗盡資源的問題,這個問題不是沒有前例的。當使用PDO的時候,你可以藉由摧毀物件來自行結束連線,以確保所有關聯到這個物件的東西都被刪除。例如,將它的值設為NULL。如果你沒有明確的將它關閉,那麼PHP會在腳本結束時自動的關閉連線,除非你使用的是持續的連線。

##與資料庫溝通 {#databases_interacting_title}

當開發者們開始學習PHP,他們常常將資料庫溝通的動作與他們的表達邏輯混合,而使得程式碼長得像這樣:

<ul>
<?php
foreach ($db->query('SELECT * FROM table') as $row) {
    echo "<li>".$row['field1']." - ".$row['field1']."</li>";
}
</ul>

這是一個很糟的例子,主要是因為它很難除蟲,很難測試,很難閱讀,而且沒有限制的話,它將會輸出過多資料。

而有非常多的方法可以達到同樣的目的,一切都看你喜歡哪種,物件導向 或是 函數編程 - 但是一定要有基本的分離。

考慮以下最基本的步驟:

<?php
function getAllFoos($db) {
    return $db->query('SELECT * FROM table');
}

foreach (getAllFoos($db) as $row) {
    echo "<li>".$row['field1']." - ".$row['field1']."</li>"; // 不好!!
}

這是一個好的開始,將這兩個程式碼放在不同的兩個檔案,你將會得到一些乾淨的分離。

創造一個類別來放置這個方法,你將會得到一個 “Model”,再創造一個簡單的 .php 檔案來放入表達的邏輯,你將會得到一個 “View”,這非常接近 MVC - 一個對多數的 frameworks 非常普遍的物件導向架構。

foo.php

<?php

$db = new PDO('mysql:host=localhost;dbname=testdb;charset=utf8', 'username', 'password');

// 使你的model可以被使用
include 'models/FooModel.php';

// 創造一個實例
$fooList = new FooModel($db);

// 展示你的view
include 'views/foo-list.php';

models/FooModel.php

<?php
class Foo()
{
    protected $db;

    public function __construct(PDO $db)
    {
        $this->db = $db;
    }

    public function getAllFoos() {
        return $this->db->query('SELECT * FROM table');
    }
}

views/foo-list.php

<? foreach ($fooList as $row): ?>
    <?= $row['field1'] ?> - <?= $row['field1'] ?>
<? endforeach ?>

這是對許多現代的frameworks非常基本的架構,你不需要每次都做這些,但是將表達邏輯和資料庫溝通的部分混合,在想要做單元測試的情況下,這是一個非常真實的問題

PHPBridge 有個叫 Creating a Data Class 的資源來說明非常相似的主題,而且對剛開始接觸這些概念的程式開發者而言,這是一個不錯的開始。

抽象層

許多的framework提供他們自己的抽象層,有些會建立在PDO的上層,這些抽象層常常會藉由將你的資料庫請求包裝起來,以模擬一些非你正在使用的資料庫所擁有的特色,提供你一個真正的資料庫抽象,而不是僅僅是PDO所提供的連線抽象。這些抽象層當然地會造成一些小小的效能負擔,但是你如果你正在建立一個應用程式需要與MySQL, PostgreSQL和SQLite的時候,這些代價來交換程式碼的乾淨是值得的。

有些抽象層是建立在 PSR-0PSR-4 的命名空間標準,所以可以被安裝在任何你喜歡的應用程式裡。

Back to Top

使用樣板

樣板提供簡便的方式,將展現邏輯從控制器與業務邏輯中分離出來。

一般而言,樣板包含了應用程式的HTML,但也可以套用在其他的格式上,例如XML。

樣板通常也被當作「視圖」, 而它是 模型-視圖-控制器 (MVC) 軟體架構模式第二個元素的一部份

好處

使用樣板的主要好處是可以將展現邏輯與應用程式的其他部份明確分離。樣板的唯一責任便是顯示轉化過的內容。它不負責查詢、保存資料或是其他複雜的任務。這促成了更乾淨、更具可讀性的程式碼,在開發者專注伺服器端程式(控制器、模型),而設計師負責用戶端程式(網頁標記)的團隊協作環境中,樣板導入尤其有用。

樣板同時也改善了前端程式碼的組織架構。一般而言,樣板放置在「視圖」的資料夾中,每個樣板定義在各自獨立的檔案中。這種方式鼓勵程式碼重用,它將大區塊的程式碼分為較小、可重用的段落,通常稱作局部樣板 (partials) 。舉例來說,網站的頭、尾區塊可以各自定義為樣板,之後將它們置於每一個頁面樣板的上、下位置。

最後,端視你所使用的函式庫,樣板可以透過自動逸脫使用者產出內容,帶來更多的安全性。有些函式庫甚至提供沙箱機制,樣板設計師只能使用在白名單中的變數和函式。

原生 PHP 樣板

原生 PHP 樣板是直接拿 PHP 來寫樣板。這是很直覺的選擇,因為 PHP 事實上就是個樣板語言。這代表你可以在其他的語言中結合 PHP 使用,像是 HTML。這對 PHP 開發者相當有利,不需要額外學習新的語法,他們熟知可以使用的函式,所使用的編輯器也已經內建語法高亮和自動補完。此外,原生 PHP 樣板少了編譯階段,速度更快。

現今的 PHP 框架都會使用一些樣板系統,這當中多數預設使用原生的 PHP 語法。在框架之外,一些函式庫如 PlatesAura.View ,提供現今樣板常見功能,像是繼承、佈局、擴充套件等,讓原生PHP樣板更容易使用。

原生 PHP 樣板的範例

使用 Plates 函式。

<?php // user_profile.php ?>

<?php $this->insert('header', ['title' => 'User Profile']) ?>

<h1>User Profile</h1>
<p>Hello, <?=$this->escape($name)?></p>

<?php $this->insert('footer') ?>

原生 PHP 樣板使用繼承的範例

使用 Plates 函式。

<?php // template.php ?>

<html>
<head>
    <title><?=$title?></title>
</head>
<body>

<main>
    <?=$this->section('content')?>
</main>

</body>
</html>
<?php // user_profile.php ?>

<?php $this->layout('template', ['title' => 'User Profile']) ?>

<h1>User Profile</h1>
<p>Hello, <?=$this->escape($name)?></p>

編譯樣板

PHP 不斷進化為成熟、物件導向的語言,但它作為樣板語言沒有改善多少。編譯樣板,像是 TwigSmarty*,提供樣板專用的新語法,補足了這片空白。從自動逸脫到繼承以及簡化控制結構,編譯樣板設計地更容易撰寫、可讀性更高、使用上更為安全。編譯樣板更能在不同的語言中使用,Mustache 就是一個極佳例子。由於這些樣板需要編譯,在效能方面會帶來些許影響,不過如果適當使用快取,影響就十分有限。

*雖然 Smarty 提供自動逸脫機制,不過這個功能並非預設啟用。

編譯樣板範例

使用 Twig 函式庫。

{% include 'header.html' with {'title': 'User Profile'} %}

<h1>User Profile</h1>
<p>Hello, {{ name }}</p>

{% include 'footer.html' %}

編譯模例使用繼承的範例

使用 Twig 函式庫。

// template.php

<html>
<head>
    <title>{% block title %}{% endblock %}</title>
</head>
<body>

<main>
    {% block content %}{% endblock %}
</main>

</body>
</html>
// user_profile.php

{% extends "template.html" %}

{% block title %}User Profile{% endblock %}
{% block content %}
    <h1>User Profile</h1>
    <p>Hello, {{ name }}</p>
{% endblock %}

延伸閱讀

文章與指南

函式庫

Back to Top

錯誤與例外

錯誤

在許多「重例外」(exception-heavy) 程式語言中,一旦發生錯誤,就會丟出例外。這的確是可行的運作方式,不過 PHP 卻是「輕例外」(exception-light) 語言。當然它具備例外,在和物件作用時,核心也開始採用這個機制來處理,只是PHP 會盡其可能保持運作,無視發生的事情,除非是嚴重錯誤。

舉例而言:

$ php -a
php > echo $foo;
Notice: Undefined variable: foo in php shell code on line 1

這裡只是 notice 層級的錯誤, PHP 仍然會愉快地繼續執行。這對來自「重例外」語言的人可能會帶來困擾,例如在 Python 中,存取一個不存在的變數會丟出例外:

$ python
>>> print foo
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'foo' is not defined

本質上的差異在於任何小事都會驚嚇到 Python,因此開發人員可以確信任何潛在的問題或邊緣案例都能捕捉到,相同的情況 PHP 仍然會保持運作,除非極端的問題發生,這時才會丟出錯誤並回報。

錯誤嚴重性

PHP 有幾個錯誤嚴重性等級。三個最常見的訊息類型是錯誤(error)、通知(notice)和警告(warning)。它們有不同的嚴重性:「E_ERROR」、「E_NOTICE」和「E_WARNING」。錯誤是執行期的嚴重問題,通常是因為程式碼出錯而造成,而且必須修正它,因為它會使 PHP 停止運作。警告是非嚴重性的錯誤,程式執行不會因此而中止。通知是建議性質的訊息,是因為程式碼在執行期有潛在機會造成問題,程式不會因此停止。

另一個在編譯時期回報錯誤的訊息類型是「E_STRICT」。這個訊息用來建議修改程式碼,才能維持最佳的運作性並能與日後的 PHP 版本相容。

改變 PHP 的錯誤回報行為

錯誤回報可以藉由 PHP 設定及/或函式呼叫變更。使用內建的 PHP 函式 「error_reporting()」,可以設定程式執行期間的錯誤等級,方法是傳遞預先定義的錯誤等級常數,意謂如果你只想看到警告和錯誤(而非通知),那你可以這樣設定:

error_reporting(E_ERROR | E_WARNING);

你也可以決定錯誤是否在螢幕上顯示(開發時好用)或隱藏後記錄下來(適用於正式環境)。想知道更多細節,可以查看 錯誤報告 章節。

行內錯誤抑制

你可以讓 PHP 利用錯誤控制運算子「@」來抑制特定的錯誤。將這個運算子放置在表達式之前,其後的任何錯誤都不會出現。

echo @$foo['bar'];

如果「$foo[‘bar’]」存在,程式會將結果輸出,不過如果變數「$foo」或「bar」鍵值不存在,將會回傳空值或不輸出任何東西。假如不使用錯誤控制運算子,這個運算式會建立一個錯誤訊息「PHP Notice: Undefined variable: foo」或「PHP Notice: Undefined index: bar」。

這看起來像是個好主意,不過卻也有些討厭的代價。PHP 處理使用「@」的運算式,比起不用時會犧牲一些效能。過早最佳化在所有程式中也許都是爭論點,不過效能在你的應用程式/函式中佔有重要地位,那麼了解錯誤控制運算子的效能影響就很重要。

其次,錯誤控制運算子 完全 吃掉錯誤。不但沒有顯示,而且也不會記錄在錯誤記錄中。此外,在正式環境 PHP 沒有辦法關閉錯誤控制運算子。也許你沒錯,那些錯誤是無害的,不過有些較具傷害性的錯誤同時也被隱藏。

如果有方法可以避免錯誤抑制運算子,你應該考慮使用,舉例來說,上面的程式碼可以這樣重寫:

echo isset($foo['bar']) ? $foo['bar'] : '';

「fopen()」載入檔案失敗時,也許是一個使用錯誤抑制的合理例子。你可以在嘗試載入檔案前檢查是否存在,但是這個檔案如果在檢查後才被刪除,而「fopen()」還沒執行(聽起來有點不太可能,但就是會發生),這時「fopen()」將會回傳false 並且 丟出錯誤。這也許該由 PHP 本身來解決,但這就是一個錯誤抑制才能有效解決的例子。

前面我們提到在正式 PHP 環境沒有辦法關閉錯誤控制運算子。但是 xDebug 有「xdebug.scream」的 ini 設定,可以關閉錯誤控制運算子。你可以依照下面的方式修改「php.ini」:

xdebug.scream = On

你也可以在執行期間透過「ini_set」函式設定這個值:

ini_set('xdebug.scream', '1')

Scream」這個 PHP 擴充套件提供和 xDebug 類似的功能,只是 Scream 的 ini 設定叫做「scream.enabled」。

當你在除錯而懷疑錯誤資訊被隱藏時,這是最有用的方法。Scream 務必小心使用,而且只拿來當作暫時性的除錯工具。有許多 PHP 函式庫也許無法在錯誤運算子停用時正常運作。

ErrorException 類別

PHP 可以完美化身為「重例外」程式語言,只需要幾行程式碼就能切換過去。基本上你可以利用「ErrorException」類別丟出「錯誤」來當作「例外」,這個類別是擴充「Exception」類別而來。

這在許多現今框架中是常見的實務作法,像是 Symfony 和 Laravel 都是。Laravel 預設會透過 Whoops! 套件,將錯誤當作例外顯示出來,如果「app.debug」啟用的話;關閉則會隱藏。

丟出錯誤作為例外,在開發過程中可以更好地處理它,如果在開發時看到例外,你可以將它包在 catch 敘述中,再寫下如何處理的程式。捕捉每一個例外,都會使應用程式越來越穩固。

更多關於如何使用「ErrorException」來處理錯誤的細節,可以參考 ErrorException Class

例外

例外是許多流行語言的標準配備,但它們往往被 PHP 開發人員忽視。像 Ruby 就是一個極端重視例外的語言,無論有什麼不對勁發生,像是 HTTP 請求失敗,或是資料庫的查詢有問題,甚至像找不到圖片,Ruby (或是所使用的gem),將會丟出例外到畫面上,讓你立刻了解問題發生了。

PHP 看待這事則是相當隨意,呼叫「file_get_contents()」通常只會給出「FALSE」值和警告。許多較早的 PHP 框架像是 CodeIgniter 只是回傳 false、將訊息寫入記錄,頂多讓你使用像「$this->upload->get_error()」來檢視錯誤原因。這裡的問題出在你必須找出錯誤所在,並且檢視文件來查看這個類別使用什麼樣的錯誤方法,而不是明顯曝露錯誤。

另一個問題是發生在類別自動丟出錯誤到畫面並跳出程序。當你這樣做時,其他開發者動態處理錯誤的機會也被擋下了。例外應該丟出能讓開發人員意識到錯誤的存在,讓他們可以選擇處理的方式,例如:

<?php
$email = new Fuel\Email;
$email->subject('My Subject');
$email->body('How the heck are you?');
$email->to('guy@example.com', 'Some Guy');

try
{
    $email->send();
}
catch(Fuel\Email\ValidationFailedException $e)
{
    // 驗證失敗
}
catch(Fuel\Email\SendingFailedException $e)
{
    // 無法寄出信件
}
finally
{
    // 無論丟出什麼樣的例外都會執行,並且在正常程序繼續之前執行
}

SPL 例外

原生的「例外」類別沒有提供太多除錯脈絡給開發人員,要修正的解法是建立一個特殊的「Exception」類型,方式是從原生「Exception」類別建立一個子類別:

<?php
class ValidationException extends Exception {}

如此一來,可以加入多重 catch 區塊,並且依不同的例外分別處理。這也可以建立許多客製例外,其中有些已經避免使用在 SPL 擴充套件 提供的 SPL 例外。

舉例來說,如果你使用「__call()」魔術方法去呼叫了無效的方法,而不是丟出模糊的標準 Exception,或是建立客製例外專門處理,你可能已經幹了「throw new BadMethodCallException;」。

Back to Top

安全

Web 應用程式安全

總是有些不懷好意的人想鑽你 Web 應用程式的漏洞,所以採取一些必要的防範措施來強化 Web 應用程式的安全性是相當重要的。很幸運的, The Open Web Application Security Project (OWASP) 已經整理一份相當詳細的資安列表以及防範的對策。如果你是一個有資安意識的開發者,那麼就必須詳細的閱讀它。

密碼雜湊

每個人在建構 PHP 應用程式時終究會加入使用者登入的功能。使用者的帳號及密碼都會被儲存在資料庫,並在登入時使用該資料來驗證使用者。

在寫入資料前正確的 雜湊 他們的密碼是相當重要的一件事。使用單向雜湊函式處理密碼可以產生不可逆的密碼雜湊,而該雜湊會是一段固定長度的字串且無法逆向演算出密碼。這就代表你可以雜湊另一組密碼,並比較兩者是否來自於同一組密碼,而且你無法得知原始的密碼。如果你不將密碼雜湊,那麼當未經授權的第三者進入你的資料庫時,所有使用者的帳號資料將會一覽無遺。有些使用者可能(很不幸的)在別的服務也使用相同的密碼,所以務必要重視資訊安全的問題。

使用 password_hash 來雜湊密碼

password_hash 已經在 PHP 5.5 時加入。現在你可以使用目前 PHP 所支援的演算法中最強大的 BCrypt 。當然,未來支援更多的演算法時他就不見得會是最強的。 password_compat 函式庫的出現是為了提供 PHP >= 5.3.7 對舊版本的支援性。

在下面例子中,我們雜湊一組字串,然後和新的雜湊值做比對。因為我們使用的兩組字串是不同的( ‘secret-password’ 與 ‘bad-password’ ),所以理所當然的就登入失敗了。

<?php

require 'password.php';

$passwordHash = password_hash('secret-password', PASSWORD_DEFAULT);

if (password_verify('bad-password', $passwordHash)) {
    // Correct Password
} else {
    // Wrong password
}

資料過濾

絕對(永遠)不要在 PHP 程式相信外部輸入。每次在程式中使用外部輸入時務必要過濾及驗證。 filter_varfilter_input 函式可以過濾文字及驗證文字的格式(例如 Email )。

外部輸入可以是:由表單輸入的 $_GET$_POST ,還有 $_SERVER 超級全域變數,以及來自 fopen('php://input', 'r') 的 Http 請求。切記,外部輸入並不限定是使用者從表單填入的資料。上傳及下載的檔案, Session 值及 Cookie 資料,以及第三方 Web 服務提供的資料,均是外部輸入。

當外部輸入被儲存、合併,或是下次讀取,他們依然是外部輸入。每次在程式中處理、輸出、串接或是引入資料時,試著問你自己這些資料都已經過濾,而且可以被信任。

根據資料使用的方式不同,就要進行不同的_過濾_。例如當外部輸入未經過濾輸出到 HTML 頁面上時,他就可以在你的網站上執行 HTML 及 JavaScript !這就是我們所知道的跨網站指令碼( Cross-site scripting , 又稱為 XSS ),這是一個相當危險的攻擊手法。避免 XSS 的其中一個方式就是使用 strip_tags 函式來過濾外部輸入的所有 HTML 標籤,另外也可以使用 htmlentitieshtmlspecialchars 函式將特定字元替換成 HTML 的實體符號。

另一個例子是傳遞給終端機執行的選項,這是相當危險的一件事(而且通常是個爛主意),不過你可以使用內建的 escapeshellarg 函式來過濾執行終端機的參數。

最後一個例子就是根據外部輸入從檔案系統載入檔案,該動作可以透過修檔案名稱或是路徑來攻擊。你必須移除外部輸入的 “/” , “../” , 空字元 或是其他檔案路徑的字元,這樣就可以避免載入隱密,非公開或是敏感的檔案。

淨化

淨化就是刪除(或是跳脫)外部輸入不合法或是不安全的字元。

例如,在將外部輸入的字元輸出至 HTML 或是插入到SQL的查詢前淨化外部輸入。當你使用 PDO 綁定參數時,他會為你淨化輸入的資料。

有時需要允許外部輸入包含某些安全的 HTML 標籤,並輸出至HTML頁面上。這是很難做到的,可以試著使用其他限制較嚴格的格式,例如 Markdown 或 BBCode 。如果真的窮途末路了,可以使用 HTML Purifier 來進行淨化。

查閱淨化過濾

驗證

驗證可以確保外部輸入是你所期望的資料。例如,你可能需要在處理註冊帳號時驗證Email,電話號碼或年齡。

查閱驗證過濾

設定檔

當你的應用程式建立設定檔時,建議遵照以下最佳實踐:

註冊全域變數

注意: 從 PHP 5.4.0 開始, register_globals 的設定已經被移除且不再被支援。如果你還保留這個設定,就意味著你該更新你的應用程式了。

當開啟 register_globals 設定時,$_POST$_GET$_REQUEST 中的變數會自動註冊為全域變數,此時你的應用程式將無法辨識資料的原始來源,導致相當容易產生安全的漏洞。

例如: $_GET['foo'] 會被註冊為全域變數 $foo ,他將會覆蓋掉程式中未定義的變數。 如果你的使用的 PHP < 5.4.0 ,請__再三確認__已經把 register_globals 給__關閉__。

錯誤報告

錯誤報告可以用來找到應用程式的錯誤,但是也可能將應用程式的結構暴露在外,產生安全性的問題。為了防止這類問題的發生,你需要為你的開發環境及上線環境進行不同的設定。

開發環境

如果要在開發環境顯示錯誤訊息,你需要在 php.ini 中進行以下設定:

display_errors = On
display_startup_errors = On
error_reporting = -1
log_errors = On

設定為 -1 表示顯示任何的錯誤訊息,包括未來版本新增的錯誤類型或參數,和 PHP 5.4 中的 E_ALL 相同。 - php.net

E_STRICT 錯誤參數是在 5.3.0 中加入的,並不包含在 E_ALL 中,但是在 5.4.0 開始,就被包含進 E_ALL 裡。這是什麼意思? 這就表示如果要在 PHP 5.3 中顯示所有的錯誤訊息,你必須將參數設定為 -1E_ALL | E_STRICT

各個 PHP 版本中顯示所有錯誤的設定

線上環境

如果要在線上環境隱藏錯誤訊息,你需要在 php.ini 中進行以下設定:

display_errors = Off
display_startup_errors = Off
error_reporting = E_ALL
log_errors = On

在線上環境進行這個設定後,所有的錯誤訊息會紀錄至 Web 伺服器的錯誤日誌,但不會顯示至使用者畫面。欲了解更多的錯誤設定相關資訊,請參考 PHP 手冊:

Back to Top

測試

為你的 PHP 程式碼撰寫自動化測試被認為是個最佳實踐,可以幫助你建立良好的 應用程式。 自動化測試是個偉大的工具,它能確保你的應用程式 在改變或增加新的功能時沒有壞掉,不應該被忽略。

PHP 有一些不同種類的測試工具 (或框架) 可以使用,它們使用不同的方法 - 但他們都試圖避免手動測試和大型 QA 團隊的需求,確保最近的變更不會破壞既有的功能。

測試驅動開發

出自 維基百科:

測試驅動開發 (TDD) 是個依賴不斷重複著非常短的開發週期來運作的軟體開發流程: 首先,開發者撰寫一個定義著期望的改善或是新函式的失敗自動測試案例,接著撰寫程式碼來使測試通過,並最終重構程式碼至可以接受的標準。 Kent Beck,被認為是開發或是 ‘重新發現’ 這個技術的人,2003 年聲明 TDD 鼓勵了簡單的設計並提振了信心

你可以為你的應用程式做這一些不同種類的測試

單元測試

單元測試是個編程方法,用來確保函式、類別和方法如預期運作,從你建構它們開始持續到整個開發週期。 藉由檢查每個函式和方法的 輸入和輸出值,你可以確保內部邏輯正確地運作。 藉由使用依賴注入、建立 “mock” 類別和 stubs,你可以驗證相依關係已經正確地建立,來提高測試涵蓋率。

當你建立一個類別或函式,你應該為每一個它應該有的行為建立單元測試。 最基本你應該 確保如果你傳遞錯誤的參數給它會發生錯誤,也確保如果你傳遞合法的參數給它會正確運作。 在開發週期中,這能幫助確保當你之後修改這個類別或函式時,舊功能依然如預期運作。 唯一的替代方案是在 test.php 檔案裡面執行 var_dump(),但這沒有辦法建立一個應用程式 - 不論大的還是小的。

單元測試另外一個用處是在貢獻程式碼給開源專案的時候。 如果你可以寫個測試來展示功能壞掉 (就是失敗),然後修復它,並展示測試通過,修補程式更可能會被接受。 如果你執行一個接受 pull requests 的專案,你應該建議這是一個要求。

PHPUnit 是個為 PHP 應用程式寫單元測試的主流測試框架,但是還有一些其他選擇

整合測試

出自 維基百科:

整合測試 (有時稱作整合和測試,縮寫「I&T」) 是結合個別軟體模組作為一個組合進行測試的軟體測試階段。 它處於單元測試之後和驗證測試之前。 整合測試拿已經做過單元測試的輸入模組,把它們聚集成比較大的項目,應用整合測試計畫來測試這些比較大的項目,並交付輸出整合的系統用來系統測試。

因為使用許多同樣的原理,很多可以用來單元測試的工具也可以用來整合測試。

功能測試

有時也被稱為驗收測試,功能測試使用工具來建立自動化的 測試,並用真實的應用程式進行測試,而不只是驗證個別單元的程式碼運作正確或驗證個別單元可以跟其他單元正確地溝通。 這些工具通常使用真實的資料並模擬應用程式實際的使用者來測試。

功能測試工具

行為驅動開發

有兩種不同的行為驅動開發 (BDD): SpecBDD 和 StoryBDD。 SpecBDD 專注於程式碼技術上的行為,而 StoryBDD 專注於商業或功能的行為和互動。 這兩種 BDD 都有 PHP 的框架。

採用 StoryBDD 時,你撰寫人可以閱讀的故事來描述應用程式的行為。 接著這些故事 可以作為應用程式的實際測試案例執行。 使用在 PHP 應用程式的 StoryBDD 框架 是 Behat,它受到 Ruby 的 Cucumber 專案啟發並實作了 Gherkin DSL 來描述功能的行為。

採用 SpecBDD 時,你撰寫規格來描述實際的程式碼應該有什麼行為。 描述函式或方法應該有什麼行為,而不是測試函式或方法。 PHP 提供 PHPSpec 框架來達到這個目的。 這個框架受到 Ruby 的 RSpec 專案 啟發。

BDD 連結

其他測試工具

除了個別的測試驅動和行為驅動框架之外,還有一些通用的框架和輔助函式庫,對任何的測試方法都很有用。

工具的超連結

Back to Top

伺服器和部署

PHP 應用程式可以用各種方式進行部署。

Platform as a Service (PaaS)

PaaS 在 web 上提供了 PHP 應用程式所需的系統和網路架構。意即不需要提供設定檔發佈 PHP 應用程式或框架。

最近 PaaS 成為用以部署,以及擴展不同大小的 PHP 應用程式的一種流行方法。你可以在我們的 資源一覽 找到 PHP PaaS “Platform as a Service” 供應商 的列表。

虛擬或自架伺服器

如果你傾向自己管理系統,或是有興趣學習,虛擬或自己的伺服器環境可以讓你自己控制應用程式的上線環境。

nginx 和 PHP-FPM

PHP 透過內建的 FastCGI Process Manager( FPM ),可以和 nginx 搭配的很好( nginx 是個輕量,高效能的伺服器)。它比 Apache 消耗更少的記憶體,且可以處理更多並行請求。這在沒有太多記憶體空間可用虛擬機器裡尤其重要。

Apache 和 PHP

PHP 和 Apache 的搭配使用已經蠻久了, Apache 可以進行高度客製並且有很多模組 可以擴展功能。對於共用伺服器,或想簡單設定 PHP 框架,以及使用開源程式像 WordPress,它是個流行的選擇。不幸的是, Apache 先天上要比 nginx 耗費更多資源並且無法同時應付更多的使用者。

Apache 有很多設定方式可以執行 PHP 。最普遍且最簡單的設定方式是使用 prefork MPM 加上 mod_php5 。雖然它不是對記憶體最高效的方式,但它是最簡單使用的方式。如果你不想在伺服器系統管理方面太過深入,這可能是最好的選擇。注意如果你使用 mod_php5 ,你也必須使用 prefork MPM。

另外,如果你想讓 Apache 有更好的效能和更多的穩定性,那麼可以使用像是 nginx FPM 的 worker MPM 或是 event MPM 搭配 mod_fastcgi 或 mod_fcgid 。兩者在設定上對於記憶體使用更為高效並且更快,但是設定上需要花更多功夫。

共用伺服器

PHP 相當普遍,很少有伺服器沒有內建 PHP ,但是要確認它是最新的版本。 共用伺服器讓你可以讓你和其他開發者在同一台機器上部署網站。好處是這已經是一種便宜的方式,壞處是 伺服器的負載可能下降或是有安全性漏洞是主要考量。如果預算足夠,你應該要避開它。

建立及部署應用程式

如果你發現自己在(手動)上傳檔案前,需要手動處理資料庫 schema 改變,或是手動執行測試,先想一想!在更新版本時,每個額外的人工部署任務都會增加嚴重錯誤的可能性。當你進行簡單的更新時,或是建構網站,或是持續整合( continuous integration )策略, 自動化部署 會是你的好朋友。

你可能會想要自動化的任務有:

Build Automation Tools

Build tools can be described as a collection of scripts that handle common tasks of software deployment. The build tool is not a part of your software, it acts on your software from ‘outside’.

There are many open source tools available to help you with build automation, some are written in PHP others aren’t. This shouldn’t hold you back from using them, if they’re better suited for the specific job. Here are a few examples:

Phing is the easiest way to get started with automated deployment in the PHP world. With Phing you can control your packaging, deployment or testing process from within a simple XML build file. Phing (which is based on Apache Ant) provides a rich set of tasks usually needed to install or update a web app and can be extended with additional custom tasks, written in PHP.

Capistrano is a system for intermediate-to-advanced programmers to execute commands in a structured, repeatable way on one or more remote machines. It is pre-configured for deploying Ruby on Rails applications, however people are successfully deploying PHP systems with it. Successful use of Capistrano depends on a working knowledge of Ruby and Rake.

Dave Gardner’s blog post PHP Deployment with Capistrano is a good starting point for PHP developers interested in Capistrano.

Chef is more than a deployment framework, it is a very powerful Ruby based system integration framework that doesn’t just deploy your app but can build your whole server environment or virtual boxes.

Chef resources for PHP developers:

Continuous Integration

Continuous Integration is a software development practice where members of a team integrate their work frequently, usually each person integrates at least daily — leading to multiple integrations per day. Many teams find that this approach leads to significantly reduced integration problems and allows a team to develop cohesive software more rapidly.

– Martin Fowler

There are different ways to implement continuous integration for PHP. Recently Travis CI has done a great job of making continuous integration a reality even for small projects. Travis CI is a hosted continuous integration service for the open source community. It is integrated with GitHub and offers first class support for many languages including PHP.

Further reading:

Back to Top

快取

PHP 本身非常快速,但是在遇到諸如遠端連線,載入檔案時會有瓶頸。好佳在,有很多工具可以加速這些部分的處理,或是減少進行這些耗時任務的次數。

Bytecode 快取

當 PHP 檔案被執行時,會先被編碼成 bytecode ,(也被稱為 opcode ),然後才會執行這些 bytecode。如果 PHP 檔案沒有被修改,編成的 bytecode 也不會改變。這意味這編碼這一步驟是浪費 CPU 資源的。

這就是 Bytecode 快取的用途。它會將編好 bytecode 存在記憶體,並且在往後的呼叫中重複使用,以防止多餘的編譯。 設定 bytecode 快取僅需要花上幾分鐘,然後應用程式的執行速度就能得到飛快的提升。實在沒有理由不使用它。

在 PHP 5.5 之後,內建有 bytecode 快取, OPcache ,更早的版本也可以使用。

其他流行的 bytecodes 快取:

物件快取

快取程式碼裡面的物件常常是很有助益的,像是取得時很耗資源的資料,或是資料庫裡不太會變動的資料。你可以使用物件快取系統來保存這些資料以供日後快速取用。如果你在取出這些資料後可以儲存它們,那麼在往後的請求裡就可以直接使用這些資料,從而獲得效能的提升並減少資料庫的負擔。

有很多流行的 bytecode 快取同時也可以讓你儲存自定義資料,所以這或許是一個更好的理由去使用他們。 PCu , XCache , 和 WinCache 都有 APIs 可以將資料從 PHP 程式碼存到記憶體。

最常用的記憶體資料快取系統是 APCu 和 memcached 。 APCu 是儲存資料很好的選擇之一,他有很簡單的 API 讓你把資料加到記憶體快取,並且非常容易設定和使用。但是 APCu 的一個限制是只能用在其安裝的伺服器上。 另一方面, Memcached 可以作為獨立的服務,可以從網路存取,意味著你可以將資料可以存在 hyper-fast 資料儲存中心以及很多不同可以取得資料的系統。

注意如果在伺服器跑的是 PHP (Fast-)CGI ,每個 PHP 行程會有自己的快取,也就是說, APCu 的資料不會在工作行程( worker processes )間共用,因為它不會綁定到 PHP 的行程。

在單一網路環境內, APCu 的存取速度通常會表現的比 memcached 好,但是 memcached 可以很快速的進行擴展。如果你不打算用多伺服器架設你的應用程式,或是不需要 memcached 特有的功能, APCu 或許是快取資料的最好選擇。

APCu 的使用範例:

<?php
// 卻認是否有稱作 'expensive_data' 的資料存在快取裡
$data = apc_fetch('expensive_data');
if ($data === false) {
    // 如果資料沒有被快取,把耗費昂貴成本取得的資料存起來供往後使用
    apc_add('expensive_data', $data = get_expensive_data());
}

print_r($data);

注意在 PHP 5.5 之前, APC 同時提供了資料快取和 bytecode 快取。 APCu 是用在 PHP 5.5+ 資料快取的 APC 分支,因為 PHP 已經有內建的bytecode 快取了。

更多流行的資料快取系統:

Back to Top

資源

從資源開始

該關注的人

指導

PHP 的 PaaS 提供商

框架

許多的 PHP 開發者都使用框架,而不是重新造輪子來建構 Web 的應用程式。框架抽象掉許多底層常用的商業邏輯,並提供有益又簡便的方法來完成常見的任務。

你並不一定要在每個專案都使用框架。有時原生的 PHP 才是正確的選擇,但如果你需要一個框架,那麼有三種主要類型可供使用:

微型框架基本上是一個包裝好的程式,並用來路由 HTTP 請求至一個回呼,控制器,或方法等等,盡可能地加快開發的速度,有時還能使用一些函式庫來幫助開發,例如一個基本的資料庫函式庫等等。他們卓越的用來建構 HTTP 的服務。

許多的框架會在微型框架上加入相當多可用的功能,我們則稱之為大型框架。這些框架通常會提供 ORMs ,分份驗證套件等等。

套件框架是多個獨立函式庫所結合起來的。不同的組件框架可以一起相容使用在微型或是大型框架。

套件

正如同標題提到,「套件」是另一種建立,發布及推動開源碼的方式。現在存在著各種的套件庫,其中最主要的兩個為:

這兩個套件庫都有用來安裝及升級的終端機程式,這部分已經在[相依管理]中敘述過。

此外,還有套件框架的套件提供商提供不包含框架的套件。這些項目通常和其他的套件或特定的框架沒有相依關係。

例如,你可以使用 [FuelPHP 驗證函式庫],而不使用 FuelPHP 整個框架。

Laravel 的 Illuminate 套件 和 Laravel 框架將變得越來越沒有相依關係。 現在我們只列出和 Laravel 框架最沒有相依關係的套件。

書籍

PHP 現在有相當多的書,但有點遺憾的是已經很舊,所以資料不再是正確的。甚至還有書商發表「 PHP 6 」,這是不存在的書,而且永遠不會出現。因為那些書,所以 PHP 的下一個版本為「 PHP 7 」。

這個章節的目的是針對一般 PHP 的開發,並建議選用內容生動的書。如果你想在這加入你的書,請發送一個 PRs ,我們將會審查你提供的內容是否有關聯性。

免費書籍

Back to Top

社群

PHP 社群型態多元而且規模龐大,成員也樂意並隨時準備好幫助新進的開發人員。你可考慮加入當地的 PHP 使用者社群(PUG, PHP user group),或是參加較大型的 PHP 研討會,學習更多這裡提到的最佳實踐。你也可以使用 IRC 逛逛 #phpc 頻道 irc.freenode.com,也可以跟隨 Twitter 帳號 @phpc。試著走出去認識一些新的開發者,學習新的議題,總之,交些新朋友!其他的社群資源還包含 Google+ PHP Programmer communityStackOverflow

查看官方 PHP 活動行事曆

PHP 使用者社群

如果你住在較大的城市,附近應該就會有 PHP 使用者社群。雖然沒有官方的 PUG 列表,不過應該很容易就可以透過 GoogleMeetup.comPHP.ug 搜尋到本地的 PUG。如果你住在較小型的城鎮,當地也許還沒有 PUG,如果是這種情形,不妨就開始組織一個。

了解關於 PHP Wiki 上的使用者社群

PHP 研討會

世界各地的 PHP 社群也會舉辦一些較大型的區域性或國際性的研討會,知名的社群成員通常會在這些大型活動中現身演講,這是直接跟業界領袖直接學習的好機會。

查詢 PHP 研討會

Back to Top

PHPDoc

PHPDoc 是註解 PHP 程式碼的非正式標準。它有許多不同的標記可以使用。完整的標記列表和範例可以查看 PHPDoc 指南。

底下是撰寫類別方法時的一種寫法:

<?php
/**
 * @author A Name <a.name@example.com>
 * @link http://www.phpdoc.org/docs/latest/index.html
 * @package helper
 */
class DateTimeHelper
{
    /**
     * @param mixed $anything Anything that we can convert to a \DateTime object
     *
     * @return \DateTime
     * @throws \InvalidArgumentException
     */
    public function dateTimeFromAnything($anything)
    {
        $type = gettype($anything);

        switch ($type) {
            // Some code that tries to return a \DateTime object
        }

        throw new \InvalidArgumentException(
            "Failed Converting param of type '{$type}' to DateTime object"
        );
    }

    /**
     * @param mixed $date Anything that we can convert to a \DateTime object
     *
     * @return void
     */
    public function printISO8601Date($date)
    {
        echo $this->dateTimeFromAnything($date)->format('c');
    }

    /**
     * @param mixed $date Anything that we can convert to a \DateTime object
     */
    public function printRFC2822Date($date)
    {
        echo $this->dateTimeFromAnything($date)->format('r');
    }
}

這個類別說明首先使用 @author 標記,它是用來說明程式碼的作者,在多位開發者的情況下,可以同時列出好幾位。其次使用 @link 標記,它提供連一個網站連結,連結到進一步說明程式碼的網站。第三個標記使用了 @package,用來分類組織程式碼。

在這個類別中,第一個方法的 @param 標記,說明類型、名稱和傳入方法的參數。此外,@return@throws 標記說明回傳類型以及可能會丟出的例外。

第二、第三個方法非常類似,和第一個方法一樣使用一個 @param 標記。第二、和第三個方法之間關鍵差別在註解區塊使用/排除 @return 標記。「@return void」標記明確告訴我們沒有回傳值,而過去省略「@return void」宣告也具有相同效果(沒有回傳任何東西)。

Back to Top