php之道

 

PHP

The Right Way.

php

歡迎

目前網絡上充斥着大量的過期資訊,讓 PHP 新手誤入歧途,而且傳播着錯誤的實踐以及不安全的代碼。PHP 之道 收集了現有的 PHP 最佳實踐、編碼規範和權威學習指南,方便 PHP 開發者閱讀和查找html

使用 PHP 沒有規範化的方式。本網站主要是向 PHP 新手介紹一些他們沒有發現或者是太晚發現的主題, 或是經驗豐富的專業人士已經實踐已久的作法提供一些新想法。本網站也不會告訴您應該使用什麼樣的工具,而是提供多種選擇的建議,並儘量地說明方法及用法上的差別。前端

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

翻譯

PHP 之道 已經翻譯成多種語言:mysql

如何貢獻

幫助咱們讓本網站做爲 PHP 新手的最佳資源!在 GitHub 上貢獻nginx

推廣

您能夠在網站上放置 PHP之道 的橫幅來支持咱們,讓 PHP 的新人知道哪裏能夠獲取到好的資料!laravel

廣告橫幅git

Back to Top程序員

入門指南

使用當前穩定版本 (5.6)

若是你剛開始學習 PHP,請使用最新的穩定版本 PHP 5.6。PHP 近年來有了巨大的改進,增長了許多強大的 新特性。雖然 5.2 和 5.6 之間增長的版本號彷佛很小, 但它表明了 重大的 改進。若是你想查找一個函數及其用法,能夠去官方手冊 php.net 中查找。github

內置的 web 服務器

PHP 5.4 以後, 你能夠不用安裝和配置功能齊全的 Web 服務器,就能夠開始學習 PHP。 要啓動內置的 Web 服務器,須要從你的命令行終端進入項目的 Web 根目錄,執行下面的命令:

> php -S localhost:8000

 

Mac 安裝

OS X 系統會預裝 PHP, 只是通常狀況下版本會比最新穩定版低一些。目前 Lion 是 5.3.10, Mavericks 是 5.4.17, Yosemite 則是 5.5.9, 但在 PHP 5.6 出來以後, 這些每每是不夠的。

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

經過 Homebrew 安裝 PHP

Homebrew 是一個強大的 OS X 專用包管理器, 它能夠幫助你輕鬆的安裝 PHP 和各類擴展。 Homebrew PHP 是一個包含與 PHP 相關的 Formulae,能讓你經過 homebrew 安裝 PHP 的倉庫。

也就是說, 你能夠經過 brew install 命令安裝 php53php54php55 或者 php56 ,而且經過修改 PATH 變量來切換各個版本。或者你也能夠使用 brew-php-switcher 來自動切換。

經過 Macports 安裝 PHP

MacPorts 是一個開源的,社區發起的項目,它的目的在於設計一個易於使用的系統,方便編譯,安裝以及升級 OS X 系統上的 command-line, X11 或者基於 Aqua 的開源軟件。

MacPorts 支持預編譯的二進制文件,所以你沒必要每次都從新從源碼壓縮包編譯,若是你的系統沒有安裝這些包,它會節省你不少時間。

此時,你能夠經過 port install 命名來安裝 php53php54php55 或者 php56,好比:

sudo port install php54
sudo port install php55

 

你也能夠執行 select 命令來切換當前的 php 版本:

sudo port select --set php php55

 

經過 phpbrew 安裝 PHP

phpbrew 是一個安裝與管理多個 PHP 版本的工具。它在應用程序或者項目須要不一樣版本的 PHP 時很是有用,讓你再也不須要使用虛擬機來處理這些狀況。

經過 Liip’s binary installer 安裝 PHP

php-osx.liip.ch 是另外一種流行的選擇,它提供了從5.3到5.6版本的單行安裝功能。 它並不會覆蓋Apple集成的PHP文件,而是將其安裝在了一個獨立的目錄中(/usr/local/php5)。

源碼編譯

另外一個讓你控制安裝 PHP 版本的選擇就是 自行編譯。 若是使用這種方法, 你必須先確認是否已經經過 「Apple’s Mac Developer Center」 下載、安裝 Xcode 或者 「Command Line Tools for XCode」

集成包 (All-in-One Installers)

上面列出的解決方案主要是針對 PHP 自己, 並不包含:好比 Apache,Nginx 或者 SQL 服務器。 集成包好比 MAMP 和 XAMPP 會安裝這些軟件而且將他們綁在一塊兒,不過易於安裝的背後也犧牲了必定的彈性。

Windows 安裝

你能夠從 windows.php.net/download 下載二進制包。 解壓後, 最好爲你的 PHP 所在的根目錄(php.exe 所在的文件夾)設置 PATH,這樣就能夠從命令行中直接執行 PHP。

Windows 下有多種安裝 PHP 的方式,你能夠 下載二進制安裝包 並使用 .msi 安裝程序。從 PHP 5.3.0 以後,這個安裝程序將再也不提供下載支持。

若是隻是學習或者本地開發,能夠直接使用 PHP 5.4+ 內置的 Web 服務器, 還能省去配置服務器的麻煩。若是你想要包含有網頁服務器以及 MySql 的集成包,那麼像是Web Platform Installer,XAMPPEasyPHP 和 WAMP 這類工具將會幫助你快速創建 Windows 開發環境。不過這些工具將會與線上環境有些許差異,若是你是在 Windows 下開發,而生產環境則部署至 Linux ,請當心。

若是你須要將生產環境部署在 Windows 上,那 IIS7 將會提供最穩定和最佳的性能。你能夠使用 phpmanager (IIS7 的圖形化插件) 讓你簡單的設置並管理 PHP。IIS7 也有內置的 FastCGI ,你只須要將 PHP 配置爲它的處理器便可。更多詳情請見dedicated area on iis.net

Back to Top

代碼風格指南

PHP 社區百花齊放,擁有大量的函數庫、框架和組件。PHP 開發者一般會在本身的項目中使用若干個外部庫,所以 PHP 代碼遵循(儘量接近)同一個代碼風格就很是重要,這讓開發者能夠輕鬆地將多個代碼庫整合到本身的項目中。

PHP標準組 提出併發布了一系列的風格建議。其中有部分是關於代碼風格的,即 PSR-0PSR-1PSR-2 和 PSR-4。這些推薦只是一些被其餘項目所遵循的規則,如 Drupal, Zend, Symfony, CakePHP, phpBB, AWS SDK, FuelPHP, Lithium 等。你能夠把這些規則用在本身的項目中,或者繼續使用本身的風格。

一般狀況下,你應該遵循一個已知的標準來編寫 PHP 代碼。多是 PSR 的組合或者是 PEAR 或 Zend 編碼準則中的一個。這表明其餘開發者可以方便的閱讀和使用你的代碼,而且使用這些組件的應用程序能夠和其餘第三方的組件保持一致。

你能夠使用 PHP_CodeSniffer 來檢查代碼是否符合這些準則,文本編輯器 Sublime Text 的插件也能夠提供實時檢查。

你能夠經過如下兩個工具來自動修正你的程序語法,讓它符合標準。 一個是 PHP Coding Standards Fixer,它具備良好的測試。 另一個工具是 php.tools, 它是 sublime text 的一個很是流行的插件sublime-phpfmt,雖然比較新,可是在性能上有了很大的提升,這意味着實時的修復語法會更加的流暢。

你也能夠手動運行 phpcs 命令:

phpcs -sw --standard=PSR2 file.php

 

它會顯示出相應的錯誤以及如何修正的方法。一樣地,這條命令也能夠用在 git hook 中,若是你的分支代碼不符合選擇的代碼標準則沒法提交。

全部的變量名稱以及代碼結構建議用英文編寫。註釋能夠使用任何語言,只要讓如今以及將來的小夥伴可以容易閱讀理解便可。

Back to Top

語言亮點

編程範式

PHP 是一個靈活的動態語言,支持多種編程技巧。這幾年一直不斷的發展,重要的里程碑包含 PHP 5.0 (2004) 增長了完善的面向對象模型,PHP 5.3 (2009) 增長了匿名函數與命名空間以及 PHP 5.4 (2012) 增長的 traits。

面向對象編程

PHP 擁有完整的面向對象編程的特性,包括類,抽象類,接口,繼承,構造函數,克隆和異常等。

函數式編程 Functional Programming

PHP 支持函數是」第一等公民」,即函數能夠被賦值給一個變量,包括用戶自定義的或者是內置函數,而後動態調用它。函數能夠做爲參數傳遞給其餘函數(稱爲高階函數),也能夠做爲函數返回值返回。

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-4 提供了一種命名空間的推薦使用方式,它提供一個標準的文件、類和命名空間的使用慣例,進而讓代碼作到隨插即用。

2014 年 10 月,PHP-FIG 廢棄了上一個自動加載標準: PSR-0,而採用新的自動加載標準 PSR-4。但 PSR-4 要求 PHP 5.3 以上的版本,而許多項目都仍是使用 PHP 5.2,因此目前二者都能使用。若是你在新應用或擴展包中使用自動加載標準,應優先考慮使用 PSR-4。

PHP 標準庫

PHP 標準庫 (SPL) 隨着 PHP 一塊兒發佈,提供了一組類和接口。包含了經常使用的數據結構類 (堆棧,隊列,堆等等),以及遍歷這些數據結構的迭代器,或者你能夠本身實現 SPL 接口。

命令行接口

PHP 是爲開發 Web 應用而建立,不過它的命令行腳本接口(CLI)也很是有用。PHP 命令行編程能夠幫你完成自動化的任務,如測試,部署和應用管理。

CLI PHP 編程很是強大,能夠直接調用你本身的程序代碼而無需建立 Web 圖形界面,須要注意的是不要把 CLI PHP 腳本放在公開的 web 目錄下!

在命令行下運行 PHP :

> php -i

 

選項 -i 將會打印 PHP 配置,相似於 phpinfo() 函數。

選項 -a 提供交互式 shell,和 Ruby 的 IRB 或 python 的交互式 shell 類似,此外還有不少其餘有用的命令行選項

接下來寫一個簡單的 「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 是一個數組變量,包含每一個參數的, 它的第一個元素一直是 PHP 腳本的名稱,如本例中爲hello.php

命令運行失敗時,能夠經過 exit() 表達式返回一個非 0 整數來通知 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 設置成「listen for connections」模式,並訪問網址:

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

 

你的 IDE 將會攔截當前執行的腳本狀態,運行你設置的斷點並查看內存中的值。

圖形化的調試器可讓你很是容易的逐步的查看代碼、變量,以及運行時的 evel 代碼。許多 IDE 已經內置或提供了插件支持 XDebug 圖形化調試器。好比 MacGDBp 是 Mac 上的一個免費,開源的單機調試器。

Back to Top

依賴管理

PHP 有不少可供使用的庫、框架和組件。一般你的項目都會使用到其中的若干項 - 這些就是項目的依賴。直到最近,PHP 也沒有一個很好的方式來管理這些項目依賴。即便你經過手動的方式去管理,你依然會爲自動加載器而擔憂。但如今這已經再也不是問題了。

目前 PHP 有兩個使用較多的包管理系統 - Composer 和 PEAR。Composer 是 PHP 所使用的主要的包管理器,然而在很長的一段時間裏,PEAR 曾經扮演着這個角色。你應該瞭解 PEAR 是什麼,由於即便你歷來沒有使用過它,你依然有可能會碰到對它的引用。

Composer 與 Packagist

Composer 是一個傑出 的依賴管理器。在 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 安裝程序,它會執行一個全局安裝並設置你的 $PATH,因此你在任意目錄下在命令行中使用 composer

如何手動安裝 Composer

手動安裝 Compose r是一個高端的技術;僅管如此仍是有許多開發者有各類緣由喜歡使用這種交互式的應用程序安裝 Composer。在安裝前請先確認你的PHP安裝項目以下:

  • 正在使用一個知足條件的 PHP 版本
  • .phar 文件能夠正確的被執行
  • 相關的目錄有足夠的權限
  • 相關有問題的擴展沒有被載入
  • 相關的 php.ini 設置已完成

因爲手動安裝沒有執行這些檢查,你必須自已衡量決定是否值得作這些事,如下是如何手動安裝 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在composer update 時將升級你的全部依賴到你限制的最新版本。

更新通知

要接收關於新版本的更新通知。你能夠註冊 VersionEye,這個 web 服務能夠監控你的 Github 及 BitBucket 賬號中的 composer.json 文件,而且當包有新更新時會發送郵件給你。

檢查你的依賴安全問題

Security Advisories Checker 是一個 web 服務和一個命令行工具,兩者都會仔細檢查你的 composer.lock 文件,而且告訴你任何你須要更新的依賴。

處理 Composer 全局依賴

Composer 也能夠處理全局依賴和他們的二進制文件。用法很直接,你所要作的就是在命令前加上global前綴。若是你想安裝 PHPUnit 並使它全局可用,你能夠運行下面的命令:

composer global require phpunit/phpunit

 

這將會建立一個 ~/.composer 目錄存放全局依賴,要讓已安裝依賴的二進制命令隨處可用,你須要添加 ~/.composer/vendor/bin 目錄到你的 $PATH 變量。

PEAR 介紹

PEAR 是另外一個經常使用的依賴包管理器, 它跟 Composer 很相似,可是也有一些顯著的區別。

PEAR 須要擴展包有專屬的結構, 開發者在開發擴展包的時候要提早考慮爲 PEAR 定製, 不然後面將沒法使用 PEAR.

PEAR 安裝擴展包的時候, 是全局安裝的, 意味着一旦安裝了某個擴展包, 同一臺服務器上的全部項目都能用上, 固然, 好處是當多個項目共同使用同一個擴展包的同一個版本, 壞處是若是你須要使用不一樣版本的話, 就會產生衝突.

如何安裝 PEAR

你能夠經過下載 .phar 文件來安裝 PEAR. 官方文檔安裝部分 裏面有不一樣系統中安裝 PEAR 的詳細信息.

若是你是使用 Linux, 你能夠嘗試找下系統應用管理器, 舉個栗子, Debian 和 Ubuntu 有個 php-pear 的 apt 安裝包.

如何安裝擴展包

若是擴展包是在 PEAR packages list 這個列表裏面的, 你能夠使用如下命令安裝:

pear install foo

 

若是擴展包是託管到別的渠道上, 你須要 發現 (discover) 渠道先, 請見文檔 使用渠道.

使用 Composer 來安裝 PEAR 擴展包

若是你正在使用 Composer, 而且你想使用一些 PEAR 的代碼, 你能夠經過 Composer 來安裝 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 從哪一個渠道去獲取擴展包, 而後, "repositories" 部分使用下面的命名規範:

pear-channel/Package

前綴 「pear」 是爲了不衝突寫死的.

成功安裝擴展包之後, 代碼會放到項目的 vendor 文件夾中, 而且能夠經過加載 Composer 的自動加載器進行加載:

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

在代碼裏面能夠這樣使用:

<?php
$request = new pear2\HTTP\Request();

 

Back to Top

開發實踐

基礎知識

PHP 是一門龐大的語言,各個水平層次的開發者均可以利用它進行迅捷高效的開發。然而在對語言逐漸深刻的學習過程當中,咱們每每會由於走捷徑和/或不良習慣而忘記(或忽視掉)基礎的知識。爲了幫助完全解決這個問題,這一章的目的就是提醒開發人員注意有關 PHP 的基礎編程實踐。

日期和時間

PHP 中 DateTime 類的做用是在你讀、寫、比較或者計算日期和時間時提供幫助。除了 DateTime 類以外,PHP 還有不少與日期和時間相關的函數,但 DateTime 類爲大多數常規使用提供了優秀的面向對象接口。它還能夠處理時區,不過這並不在這篇簡短的介紹以內。

在使用 DateTime 以前,經過 createFromFormat() 工廠方法將原始的日期與時間字符串轉換爲對象或使用 new DateTime 來取得當前的日期和時間。使用 format() 將 DateTime 轉換回字符串用於輸出。

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

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

 

對 DateTime 進行計算時能夠使用 DateInterval 類。DateTime 類具備例如 add() 和 sub() 等將 DateInterval 看成參數的方法。編寫代碼時注意不要認爲每一天都是由相同的秒數構成的,不管是夏令時(DST)仍是時區轉換,使用時間戳計算都會遇到問題,應當選擇日期間隔。使用 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('Y-m-d') . ' ';
}

 

設計模式

當你在編寫本身的應用程序時,最好在項目的代碼和總體架構中使用通用的設計模式,這將幫助你更輕鬆地對程序進行維護,也可以讓其餘的開發者更快地理解你的代碼。

當你使用框架進行開發時,絕大部分的上層代碼以及項目結構都會基於所使用的框架,所以不少關於設計模式的決定已經由框架幫你作好了。固然,你仍是能夠挑選你最喜歡的模式並在你的代碼中進行應用。但若是你並無使用框架的話,你就須要本身去尋找適合你的應用的最佳模式了。

使用 UTF-8 編碼

本章是由 Alex Cabal 最初撰寫在 PHP Best Practices 中的,咱們使用它做爲進行建議的基礎

這不是在開玩笑。請當心、仔細而且先後一致地處理它。

目前,PHP 仍未在底層實現對 Unicode 的支持。雖然有不少途徑能夠確保 UTF-8 字符串可以被正確地處理,但這並非很簡單的事情,一般須要對 Web 應用進行全方面的檢查,從 HTML 到 SQL 再到 PHP。咱們將爭取進行一個簡潔實用的總結。

PHP 層面的 UTF-8

最基本的字符串操做,像是連結兩個字符串或將字符串賦值給變量,並不須要對 UTF-8 作特別的處理。然而大多數字符串的函數,像 strpos() 和 strlen(),確實須要特別的處理。這些函數名中一般包含 mb_*:好比,mb_strpos() 和 mb_strlen()。這些 mb_* 字符串是由 Multibyte String Extension 提供支持的,它專門爲操做 Unicode 字符串而特別進行了設計。

在操做 Unicode 字符串時,請你務必使用 mb_* 函數。例如,若是你對一個 UTF-8 字符串使用 substr(),那返回的結果中有很大可能會包含一些亂碼。正確的方式是使用 mb_substr()

最難的地方在於每次都要記得使用 mb_* 函數。若是你哪怕只有一次忘記了使用,你的 Unicode 字符串就有在接下來的過程當中變成亂碼的風險。

不是全部的字符串函數都有一個對應的 mb_* 函數。若是你想要的功能沒有對應的 mb_* 函數的話,那隻能說你運氣不佳了。

你應該在你全部的 PHP 腳本(或全局包含的腳本)的開頭使用 mb_internal_encoding() 函數,而後緊接着在會對瀏覽器進行輸出的腳本中使用 mb_http_output()。在每個腳本當中明確聲明字符串的編碼能夠免去不少往後的煩惱。

另外,許多對字符串進行操做的函數都有一個可選的參數用來指定字符串編碼。當能夠設定這類參數時,你應該始終明確指定使用 UTF-8。例如,htmlentities() 有一個字符編碼的選項,你應該始終將其設爲 UTF-8。從 PHP 5.4.0 開始, htmlentities() 和 htmlspecialchars() 的編碼都已經被默認設爲了 UTF-8。

最後,若是你所編寫的是分佈式的應用程序而且不能肯定 mbstring 擴展必定開啓的話,能夠考慮使用 patchwork/utf8 Composer 包。它會在 mbstring 可用時自動使用,不然自動切換回非 UTF-8 函數。

數據庫層面的 UTF-8

若是你使用 PHP 來操做到 MySQL,有些時候即便你作到了上面的每一點,你的字符串仍可能面臨在數據庫中以非 UTF-8 的格式進行存儲的問題。

爲了確保你的字符串從 PHP 到 MySQL都使用 UTF-8,請檢查確認你的數據庫和數據表都設定爲 utf8mb4 字符集和整理,而且確保你的 PDO 鏈接請求也使用了 utf8mb4 字符集。請看下方的示例代碼,這是 很是重要 的。

請注意爲了完整的 UTF-8 支持,你必須使用 utf8mb4 而不是 utf8!你會在進一步閱讀中找到緣由。

瀏覽器層面的 UTF-8

使用 mb_http_output() 函數來確保 PHP 向瀏覽器輸出 UTF-8 格式的字符串。

隨後瀏覽器須要接收 HTTP 應答來指定頁面是由 UTF-8 進行編碼的。之前這一步是經過在頁面 <head> 標籤下包含字符集 <meta> 標籤實現的,這是一種可行的方式。但更好的作法是在 Content-Type 響應頭中進行設置,由於這樣作的速度會更快

<?php
// Tell PHP that we're using UTF-8 strings until the end of the script
mb_internal_encoding('UTF-8');

// Tell PHP that we'll be outputting UTF-8 to the browser
mb_http_output('UTF-8');

// Our UTF-8 test string
$string = 'Êl síla erin lû e-govaned vîn.';

// Transform the string in some way with a multibyte function
// Note how we cut the string at a non-Ascii character for demonstration purposes
$string = mb_substr($string, 0, 15);

// Connect to a database to store the transformed string
// See the PDO example in this document for more information
// Note the `charset=utf8mb4` in the Data Source Name (DSN)
$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
    )
);

// Store our transformed string as UTF-8 in our database
// Your DB and tables are in the utf8mb4 character set and collation, right?
$handle = $link->prepare('insert into ElvishSentences (Id, Body) values (?, ?)');
$handle->bindValue(1, 1, PDO::PARAM_INT);
$handle->bindValue(2, $string);
$handle->execute();

// Retrieve the string we just stored to prove it was stored correctly
$handle = $link->prepare('select * from ElvishSentences where Id = ?');
$handle->bindValue(1, 1, PDO::PARAM_INT);
$handle->execute();

// Store the result into an object that we'll output later in our 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

依賴注入

出自維基百科 Wikipedia:

依賴注入是一種容許咱們從硬編碼的依賴中解耦出來,從而在運行時或者編譯時可以修改的軟件設計模式。

這句解釋讓依賴注入的概念聽起來比它實際要複雜不少。依賴注入經過構造注入,函數調用或者屬性的設置來提供組件的依賴關係。就是這麼簡單。

基本概念

咱們能夠用一個簡單的例子來講明依賴注入的概念

下面的代碼中有一個 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的,咱們能夠直接給它賦值。

複雜的問題

若是你曾經瞭解過依賴注入,那麼你可能見過 「控制反轉」(Inversion of Control) 或者 「依賴反轉準則」(Dependency Inversion Principle)這種說法。這些是依賴注入能解決的更復雜的問題。

控制反轉

顧名思義,一個系統經過組織控制和對象的徹底分離來實現」控制反轉」。對於依賴注入,這就意味着經過在系統的其餘地方控制和實例化依賴對象,從而實現瞭解耦。

一些 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 類依賴於接口,相比依賴於具體實現有更多的優點。

假設你工做的團隊中,一位同事負責設計適配器。在第一個例子中,咱們須要等待適配器設計完以後才能單元測試。如今因爲依賴是一個接口/約定,咱們能輕鬆地模擬接口測試,由於咱們知道同事會基於約定實現那個適配器

這種方法的一個更大的好處是代碼擴展性變得更高。若是一年以後咱們決定要遷移到一種不一樣的數據庫,咱們只須要寫一個實現相應接口的適配器而且注入進去,因爲適配器遵循接口的約定,咱們不須要額外的重構。

容器

你應該明白的第一件事是依賴注入容器和依賴注入不是相同的概念。容器是幫助咱們更方便地實現依賴注入的工具,可是他們一般被誤用來實現反模式設計 Service Location 。把一個依賴注入容器做爲 Service Locator 注入進類中隱式地創建了對於容器的依賴,而不是真正須要替換的依賴,並且還會讓你的代碼更不透明,最終變得更難測試。

大多數現代的框架都有本身的依賴注入容器,容許你經過配置將依賴綁定在一塊兒。這實際上意味着你能寫出和框架層一樣乾淨、解耦的應用層代碼。

Back to Top

數據庫

絕大多數時候你的 PHP 程序都須要使用數據庫來長久地保存數據。這時你有一些不一樣的選擇能夠來鏈接並與數據庫進行交互。在 PHP 5.1.0 以前,咱們推薦的方式是使用例如mysqlipgsqlmssql 等原生驅動。

原生驅動是在只使用 一個 數據庫的狀況下的不錯的方式,但若是,舉個例子來講,你同時使用了 MySQL 和一點點 MSSQL,或者你須要使用 Oracle 的數據庫,那你就不可以只使用一個數據庫驅動了。你須要爲每個數據庫去學習各自不一樣的 API — 這樣作顯然不科學。

MySQL 擴展

PHP 中的 mysql 擴展已經再也不進行新的開發了,而且已經在 PHP 5.5.0 版本中正式被廢棄,這意味着它將會在接下來的更新中被移除。若是你使用 mysql_* 開頭的函數,例如 mysql_connect()和 mysql_query() 的話,它們將會在後續的 PHP 版本沒法使用。所以在之後的某個時間你須要重寫你的代碼。最好的辦法是在你的開發計劃中使用 mysqli 或 PDO 來取代 mysql 擴展,這樣你纔不會在後面手忙腳亂。

若是你正從零開始,請必定避免使用 mysql 擴展:請選擇 MySQLi 擴展,或者使用 PDO

PDO 擴展

PDO 是一個數據庫鏈接抽象類庫 — 自 5.1.0 版本起內置於 PHP 當中 — 它提供了一個通用的接口來與不一樣的數據庫進行交互。好比你能夠使用相同的簡單代碼來鏈接 MySQL 或是 SQLite:

<?php
// 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 使你可以安全的插入外部輸入(例如 ID)到你的 SQL 請求中而沒必要擔憂 SQL 注入的問題。這能夠經過使用 PDO 語句和限定參數來實現。

咱們來假設一個 PHP 腳本接收一個數字 ID 做爲一個請求參數。這個 ID 應該被用來從數據庫中取出一條用戶記錄。下面是一個錯誤的作法:

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

 

這是一段糟糕的代碼。你正在插入一個原始的請求參數到 SQL 請求中。這將讓被黑客輕鬆地利用[SQL 注入]方式進行攻擊。想一下若是黑客將一個構造的 id 參數經過像 http://domain.com/?id=1%3BDELETE+FROM+users 這樣的 URL 傳入。這將會使 $_GET['id'] 變量的值被設爲 1;DELETE FROM users 而後被執行從而刪除全部的 user 記錄!所以,你應該使用 PDO 限制參數來過濾 ID 輸入。

<?php
$pdo = new PDO('sqlite:/path/db/users.db');
$stmt = $pdo->prepare('SELECT name FROM users WHERE id = :id');
$id = filter_input(INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT); // <-- filter your data first (see [Data Filtering](#data_filtering)), especially important for INSERT, UPDATE, etc.
$stmt->bindParam(':id', $id, PDO::PARAM_INT); // <-- Automatically sanitized for SQL by PDO
$stmt->execute();

 

這是正確的代碼。它在一條 PDO 語句中使用了一個限制參數。這將對外部 ID 輸入在發送給數據庫以前進行轉義來防止潛在的 SQL 注入攻擊。

對於寫入操做,例如 INSERT 或者 UPDATE,進行數據過濾並對其餘內容進行清理(去除 HTML 標籤,Javascript 等等)是尤爲重要的。PDO 只會爲 SQL 進行清理,並不會爲你的應用作任何處理。

你也應該知道數據庫鏈接有時會耗盡所有資源,若是鏈接沒有被隱式地關閉的話,有可能會形成可用資源枯竭的狀況。不過這一般在其餘語言中更爲常見一些。使用 PDO 你能夠經過銷燬(destroy)對象,也就是將值設爲 NULL,來隱式地關閉這些鏈接,確保全部剩餘的引用對象的鏈接都被刪除。若是你沒有親自作這件事情,PHP 會在你的腳本結束的時候自動爲你完成 —— 除非你使用的是持久連接。

數據庫交互

當開發者第一次接觸 PHP 時,一般會使用相似下面的代碼來將數據庫的交互與表示層邏輯混在一塊兒:

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

 

這從不少方面來看都是錯誤的作法,主要是因爲它不易閱讀又難以測試和調試。並且若是你不加以限制的話,它會輸出很是多的字段。

其實還有許多不一樣的解決方案來完成這項工做 — 取決於你傾向於 面向對象編程(OOP)仍是函數式編程 — 但必須有一些分離的元素。

來看一下最基本的作法:

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

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

 

這是一個不錯的開頭。將這兩個元素放入了兩個不一樣的文件因而你獲得了一些乾淨的分離。

建立一個類來放置上面的函數,你就獲得了一個「Model」。建立一個簡單的.php文件來存放表示邏輯,你就獲得了一個「View」。這已經很接近 MVC — 一個大多數框架經常使用的面向對象的架構。

foo.php

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

// Make your model available
include 'models/FooModel.php';

// Create an instance
$fooModel = new FooModel($db);
// Get the list of Foos
$fooList = $fooModel->getAllFoos();

// Show the view
include 'views/foo-list.php';

 

models/FooModel.php

<?php
class FooModel
{
    protected $db;

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

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

 

views/foo-list.php

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

 

向大多數現代框架的作法學習是頗有必要的,儘管多了一些手動的工做。你能夠並不須要每一次都徹底這麼作,但將太多的表示邏輯層代碼和數據庫交互摻雜在一些將會爲你在想要對程序進行單元測試時帶來真正的麻煩。

PHPBridge 具備一項很是棒的資源叫作建立一個數據類。它包含了很是類似的邏輯並且很是適合剛剛習慣數據庫交互概念的開發者使用。

數據庫抽象層

許多框架都提供了本身的數據庫抽象層,其中一些是設計在 PDO 的上層的。這些抽象層一般將你的請求在 PHP 方法中包裝起來,經過模擬的方式來使你的數據庫擁有一些以前不支持的功能。這種抽象是真正的數據庫抽象,而不僅僅只是 PDO 提供的數據庫鏈接抽象。這類抽象的確會增長必定程度的性能開銷,但若是你正在設計的應用程序須要同時使用 MySQL,PostgreSQL 和 SQLite 時,一點點的額外性能開銷對於代碼整潔度的提升來講仍是很值得的。

有一些抽象層使用的是PSR-0 或 PSR-4 命名空間標準,因此他們能夠安裝在任何你須要的應用程序中。

Back to Top

使用模板

模板提供了一種簡便的方式,將展示邏輯從控制器和業務邏輯中分離出來。

通常來講,模板包含應用程序的 HTML 代碼,但也能夠使用其餘的格式,例如 XML 。

模板一般也被稱爲「視圖」, 而它是 模型-視圖-控制器 (MVC) 軟件架構模式第二個元素的 一部份 。

好處

使用模板的主要好處是能夠將呈現邏輯與應用程序的其餘部分進行分離。模板的單一職責就是呈現格式化後的內容。它不負責數據的查詢,保存或是其餘複雜的任務。進一步促成了更乾淨、更具可讀性的代碼,在團隊協做開發中尤爲有用,開發者能夠專一服務端的代碼(控制器、模型),而設計師負責客戶端代碼 (網頁) 。

模板同時也改善了前端代碼的組織架構。通常來講,模板放置在「視圖」文件夾中,每個模板都放在獨立的一個文件中。這種方式鼓勵代碼重用,它將大塊的代碼拆成較小的、可重用的片斷,一般稱爲局部模板。舉例來講,網站的頭、尾區塊能夠各自定義爲一個模板,以後將它們放在每個頁面模板的上、下位置。

最後,根據你選擇的類庫,模板能夠經過自動轉義用戶的內容,從而帶來更多的安全性。有些類庫甚至提供沙箱機制,模板設計者只能使用在白名單中的變量和函數。

原生 PHP 模板

原生 PHP 模板就是指直接用 PHP 來寫模板,這是很天然的選擇,由於 PHP 自己實際上是個模板語言。這表明你能夠在其餘的語言中結合 PHP 使用,好比 HTML 。這對 PHP 開發者至關有利,由於不須要額外學習新的語法,他們熟知能夠使用的函數,而且使用的編輯器也已經內置了語法高亮和自動補全。此外,原生的 PHP 模板沒有了編譯階段,速度會更快。

現今的 PHP 框架都會使用一些模板系統,這當中多數是使用原生的 PHP 語法。在框架以外,一些類庫好比 Plates 或 Aura.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 不斷升級爲成熟的、面向對象的語言,但它做爲模板語言 沒有改善多少。編譯模板,好比 Twig 或 Smarty* ,提供了模板專用的新語法,填補了這片空白。從自動轉義到繼承以及簡化控制結構,編譯模板設計地更容易編寫,可讀性更高,同時使用上也更加的安全。編譯模板甚至能夠在不一樣的語言中使用,Mustache 就是一個很好的例子。因爲這些模板須要編譯,在性能上會帶來一些輕微的影響,不過若是適當的使用緩存,影響就變得很是小了。

*雖然 Smarty 提供了自動轉義的功能, 不過這個功能默認是關閉的

編譯模板簡單示例

使用 Twig 類庫。

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

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

{% include 'footer.html' %}

 

編譯模板使用繼承示例

使用 Twig 類庫。

// template.html

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

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

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

{% 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(),能夠設定程序執行期間的錯誤等級,方法是傳入預約義的錯誤等級常量,意味着若是你只想看到警告和錯誤(而非通知),你能夠這樣設定:

<?php
error_reporting(E_ERROR | E_WARNING);

 

你也能夠控制錯誤是否在屏幕上顯示 (開發時比較有用)或隱藏後記錄日誌 (適用於正式環境)。若是想知道更多細節,能夠查看 錯誤報告 章節。

行內錯誤抑制

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

<?php
echo @$foo['bar'];

 

若是 $foo['bar'] 存在,程序會將結果輸出,若是變量 $foo 或是 'bar' 鍵值不存在,則會返回 null 而且不輸出任何東西。若是不使用錯誤控制操做符,這個表達式會產生一個錯誤信息 PHP Notice: Undefined variable: foo 或 PHP Notice: Undefined index: bar 。

這看起來像是個好主意,不過也有一些討厭的代價。PHP 處理使用 @ 的表達式比起不用時效率會低一些。過早的性能優化在全部程序語言中也許都是爭論點,不過若是性能在你的應用程序 / 類庫中佔有重要地位,那麼瞭解錯誤控制操做符的性能影響就比較重要。

其次,錯誤控制操做符會 徹底 吃掉錯誤。不但沒有顯示,並且也不會記錄在錯誤日誌中。此外,在正式環境中 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 函數來設置這個值:

<?php
ini_set('xdebug.scream', '1')

 

「Scream」這個 PHP 擴展提供了和 xDebug 相似的功能,只是 Scream 的 ini 設置項叫作 scream.enabled 。

當你在調試代碼而錯誤信息被隱藏時,這是最有用的方法。請務必當心使用 scream ,而是把它看成暫時性的調試工具。有許多的 PHP 函數類庫代碼也許沒法在錯誤抑制操做符停用時正常使用。

錯誤異常類

PHP 能夠完美化身爲「重異常」的程序語言,只須要幾行代碼就能切換過去。基本上你能夠利用 ErrorException 類拋出「錯誤」來當作「異常」,這個類是繼承自 Exception 類。

這在大量的現代框架中是一個常見的作法,好比 Symfony 和 Laravel。Laravel 默認使用 Whoops! 擴展包來處理錯誤,若是 app.debug 啓動的話,會將錯誤當成異常顯示出來,而關閉則會隱藏。

在開發過程當中將錯誤看成異常拋出能夠更好的處理它,若是在開發時發生異常,你能夠將它包在一個 catch 語句中具體說明這種狀況如何處理。每捕捉一個異常,都會使你的應用程序愈來愈健壯。

更多關於如何使用 ErrorException 來處理錯誤的細節,能夠參考 ErrorException Class

異常

異常是許多流行編程語言的標配,但它們每每被 PHP 開發人員所忽視。像 Ruby 就是一個極度重視異常的語言,不管有什麼錯誤發生,像是 HTTP 請求失敗,或者數據庫查詢有問題,甚至找不到一個圖片資源,Ruby (或是所使用的 gems),將會拋出異常,你能夠經過屏幕馬上知道所發生的問題。

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)
{
    // 這個驅動沒法發送 email
}
finally
{
    // 不管拋出什麼樣的異常都會執行,而且在正常程序繼續以前執行
}

 

SPL 異常

原生的 Exception 類並無提供太多的調試情境給開發人員,不過能夠經過創建一個特殊的 Exception 來彌補它,方式就是創建一個繼承自原生 Exception 類的一個子類:

<?php
class ValidationException extends Exception {}

 

如此一來,能夠加入多個 catch 區塊,而且根據不一樣的異常分別處理。經過這樣能夠創建 許多自定義異常,其中有些已經在 SPL 擴展 提供的 SPL 異常中定義了。

舉例來講,若是你使用了 __call() 魔術方法去調用一個無效的方法,而不是拋出一個模糊的標準 Exception 或是創建自定義的異常處理,你能夠直接拋出 throw new BadMethodCallException;

Back to Top

安全

Web 應用程序安全

攻擊者無時無刻不在準備對你的 Web 應用程序進行攻擊,所以提升你的 Web 應用程序的安全性是很是有必要的。幸運的是,來自開放式 Web 應用程序安全項目 (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
}

 

數據過濾

永遠不要信任外部輸入。請在使用外部輸入前進行過濾和驗證。filter_var() 和 filter_input() 函數能夠過濾文本並對格式進行校驗(例如 email 地址)。

外部輸入能夠是任何東西:$_GET 和 $_POST 等表單輸入數據,$_SERVER 超全局變量中的某些值,還有經過 fopen('php://input', 'r') 獲得的 HTTP 請求體。記住,外部輸入的定義並不侷限於用戶經過表單提交的數據。上傳和下載的文檔,session 值,cookie 數據,還有來自第三方 web 服務的數據,這些都是外服輸入。

雖然外部輸入能夠被存儲、組合並在之後繼續使用,但它依舊是外部輸入。每次你處理、輸出、連結或在代碼中包含時,請提醒本身檢查數據是否已經安全地完成了過濾。

數據能夠根據不一樣的目的進行不一樣的 過濾 。好比,當原始的外部輸入被傳入到了 HTML 頁面的輸出當中,它能夠在你的站點上執行 HTML 和 JavaScript 腳本!這屬於跨站腳本攻擊(XSS),是一種頗有殺傷力的攻擊方式。一種避免 XSS 攻擊的方法是在輸出到頁面前對全部用戶生成的數據進行清理,使用 strip_tags() 函數來去除 HTML 標籤或者使用 htmlentities() 或是htmlspecialchars() 函數來對特殊字符分別進行轉義從而獲得各自的 HTML 實體。

另外一個例子是傳入可以在命令行中執行的選項。這是很是危險的(同時也是一個很差的作法),可是你能夠使用自帶的 escapeshellarg() 函數來過濾執行命令的參數。

最後的一個例子是接受外部輸入來從文件系統中加載文件。這能夠經過將文件名修改成文件路徑來進行利用。你須要過濾掉"/""../"null 字符或者其餘文件路徑的字符來確保不會去加載隱藏、私有或者敏感的文件。

數據清理

數據清理是指刪除(或轉義)外部輸入中的非法和不安全的字符。

例如,你須要在將外部輸入包含在 HTML 中或者插入到原始的 SQL 請求以前對它進行過濾。當你使用 PDO 中的限制參數功能時,它會自動爲你完成過濾的工做。

有些時候你可能須要容許一些安全的 HTML 標籤輸入進來並被包含在輸出的 HTML 頁面中,但這實現起來並不容易。儘管有一些像 HTML Purifier 的白名單類庫爲了這個緣由而出現,實際上更多的人經過使用其餘更加嚴格的格式限制方式例如使用 Markdown 或 BBCode 來避免出現問題。

查看 Sanitization Filters

有效性驗證

驗證是來確保外部輸入的是你所想要的內容。好比,你也許須要在處理註冊申請時驗證 email 地址、手機號碼或者年齡等信息的有效性。

查看 Validation Filters

配置文件

當你在爲你的應用程序建立配置文件時,最好的選擇時參照如下的作法:

  • 推薦你將你的配置信息存儲在沒法被直接讀取和上傳的位置上。
  • 若是你必定要存儲配置文件在根目錄下,那麼請使用 .php 的擴展名來進行命名。這將能夠確保即便腳本被直接訪問到,它也不會被以明文的形式輸出出來。
  • 配置文件中的信息須要被針對性的保護起來,對其進行加密或者設置訪問權限。

註冊全局變量

注意: 自 PHP 5.4.0 開始,register_globals 選項已經被移除並再也不使用。這是在提醒你若是你正在升級舊的應用程序的話,你須要注意這一點。

當 register_globals 選項被開啓時,它會使許多類型的變量(包括 $_POST$_GET 和 $_REQUEST)被註冊爲全局變量。這將很容易使你的程序沒法有效地判斷數據的來源並致使安全問題。

例如:$_GET['foo'] 能夠經過 $foo 被訪問到,也就是能夠對未聲明的變量進行覆蓋。若是你使用低於 5.4.0 版本的 PHP 的話,請 確保 register_globals 是被設爲 off 的。

錯誤報告

錯誤日誌對於發現程序中的錯誤是很是有幫助的,可是有些時候它也會將應用程序的結構暴露給外部。爲了有效的保護你的應用程序不受到由此而引起的問題。你須要將在你的服務器上使用開發和生產(線上)兩套不一樣的配置。

開發環境

爲了在開發環境中顯示全部可能的錯誤,將你的 php.ini 進行以下配置:

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

 

將值設爲 -1 將會顯示出全部的錯誤,甚至包括在將來的 PHP 版本中新增長的類型和參數。 和 PHP 5.4 起開始使用的 E_ALL 是相同的。- php.net

E_STRICT 類型的錯誤是在 5.3.0 中被引入的,並無被包含在 E_ALL 中。然而從 5.4.0 開始,它被包含在了 E_ALL 中。這意味着什麼?這表示若是你想要在 5.3 中顯示全部的錯誤信息,你須要使用 -1 或者 E_ALL | E_STRICT

不一樣 PHP 版本下開啓所有錯誤顯示

  • < 5.3 -1 或 E_ALL
  •   5.3 -1 或 E_ALL | E_STRICT
  • > 5.3 -1 或 E_ALL

生產環境

爲了在生產環境中隱藏錯誤顯示,將你的 php.ini 進行以下配置:

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

 

當在生產環境中使用這個配置時,錯誤信息依舊會被照常存儲在 web 服務器的錯誤日誌中,惟一不一樣的是將再也不顯示給用戶。更多關於設置的信息,請參考 PHP 手冊:

Back to Top

測試

爲你的 PHP 程序編寫自動化測試被認爲是最佳實踐,能夠幫助你創建良好的應用程序。 自動化測試是很是棒的工具,它能確保你的應用程序在改變或增長新的功能時不會影響現有的功能,不該該忽視。

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

測試驅動開發

Wikipedia 上的定義: > 測試驅動開發 (TDD) 是一種以很是短的開發週期不斷迭代的軟件開發過程:首先開發者對將要實現的功能或者新的方法寫一個失敗的自動化測試用例,而後就去寫代碼來經過這個測試用例,最終經過重構代碼讓一其達到可接受的水準。Kent Beck, 這個技術創造者或者說從新發現者,在2003年聲明TDD 鼓勵簡單的設計和激勵信心。

目前你能夠應用的幾種不一樣類型的測試:

###單元測試 單元測試是一種編程方法來確認函數,類和方法以咱們預期的方式來工做,單元測試會貫穿整個項目的開發週期。經過檢查各個函數和方法的輸入輸出,你就能夠保證內部的邏輯已經正確執行。經過使用依賴注入和編寫」mock」 類以及 stubs 來確認依賴被正確的使用,提升測試覆蓋率。

當你建立一個類或者一個函數,你應該爲它們的每個行爲建立一個單元測試。至少你應該確認當你輸入一個錯誤參數會觸發一個錯誤,你輸入一個有效的參數會獲得正確的結果。這會幫助你在開發週期後段對類或者函數作出修改後,確認已有的功能任然能夠正常的工做。可替代的方法是在源碼中使用 var_dump() ,但這種方法卻不能去構建一個或大或小的應用。

單元測試的其餘用處是在給開源項目貢獻代碼時。若是你寫了一個測試證實代碼有bug,而後修復它,而且展現測試的過程,這樣補丁將會更容易被接受。若是你在維護一個項目,在處理 pull request 的時候能夠將單元測試做爲一個要求。

PHPUnit 是業界PHP應用開發單元測試框架的標準,但也有其餘可選的框架:

###集成測試 Wikipedia 上的定義: > 集成測試 (有時候稱爲集成和測試,縮寫爲 I&T)是把各個模塊組合在一塊兒進行總體測試的軟件測試階段。它處於單元測試以後,驗收測試以前。集成測試將已經通過了單元測試的模塊作爲輸入模塊,組合成一個總體,而後運行集成測試用例,而後輸出一個能夠進行系統測試的系統。

許多相同的測試工具既能夠運用到單元測試,也能夠運用到集成測試。

###功能性測試 有時候也被稱之爲驗收測試,功能測試是經過使用工具來生成自動化的測試用例,而後在真實的系統上運行。而不是單元測試中簡單的驗證單個模塊的正確性和集成測試中驗證各個模塊間交互的正確性。這些工具會使用表明性的真實數據來模擬真實用戶的行爲來驗證系統的正確性。

####功能測試的工具

  • Selenium
  • Mink
  • Codeception 是一個全棧的測試框架包括驗收性測試工具。
  • Storyplayer 是一個全棧的測試框架而且支持隨時建立和銷燬測試環境。

行爲驅動開發

有兩種不一樣的行爲驅動開發 (BDD): SpecBDD 和 StoryBDD。 SpecBDD 專一於代碼的技術行爲,而 StoryBDD 專一於業務邏輯或功能的行爲和互動。這兩種 BDD 都有對應的 PHP 框架。

採用 StoryBDD 時, 你編寫可讀的故事來描述應用程序的行爲。接着這些故事能夠做爲應用程序的實際測試案例執行。Behat 是使用在 PHP 應用程序中的 StoryBDD框架,它受到 Ruby 的Cucumber 項目的啓發而且實現了 Gherkin DSL 來描述功能的行爲。

採用 SpecBDD 時, 你編寫規格來描述實際的代碼應該有什麼行爲。你應該描述函數或者方法應該有什麼行爲,而不是測試函數或者方法。PHP 提供了 PHPSpec 框架來達到這個目的,這個框架受到了 Ruby 的 RSpec project 項目的啓發。

BDD 連接

  • Behat, PHP 的 StoryBDD 框架, 受到了 Ruby’s Cucumber 項目的啓發。
  • PHPSpec, PHP 的 SpecBDD 框架, 受到了 Ruby’s RSpec 項目的啓發。
  • Codeception 是一個使用 BDD 準則的全棧測試框架。

其餘測試工具

除了個別的測試驅動和行爲驅動框架以外,還有一些通用的框架和輔助函數類庫,對任何的測試方法都頗有用。

工具地址

Back to Top

服務器與部署

部署 PHP 應用程序到生產環境中有多種方式。

Platform as a Service (PaaS)

PaaS 提供了運行 PHP 應用程序所必須的系統環境和網絡架構。這就意味着只需作少許配置就能夠運行 PHP 應用程序或者 PHP 框架。

如今,PaaS 已經成爲一種部署、託管和擴展各類規模的 PHP 應用程序的流行方式。你能夠在 資源部分 查看 PHP PaaS 「Platform as a Service」 提供商 列表。

虛擬或專用服務器

若是你喜歡系統管理員的工做,或者對這方面感興趣,虛擬或者專用服務器可讓你徹底控制本身的生產環境。

nginx 和 PHP-FPM

PHP 經過內置的 FastCGI 進程管理器(FPM),能夠很好的與輕量級的高性能 web 服務器 nginx 協做使用。nginx 比 Apache 佔用更少內存並且能夠更好的處理併發請求,這對於並無太多內存的虛擬服務器尤爲重要。

Apache 和 PHP

PHP 和 Apache 有很長的合做歷史。Apache 有很強的可配置性和大量的 擴展模塊 。是共享主機中常見的Web服務器,完美支持各類 PHP 框架和開源應用(如 WordPress )。惋惜的是,默認狀況下,Apache 會比 nginx 消耗更多的資源,並且併發處理能力不強。

Apache 有多種方式運行 PHP,最多見的方式就是使用 mode_php5 的 prefork MPM 方式。可是它對內存的利用效率並不高,若是你不想深刻服務器管理方面學習,那麼這種簡單的方式多是你最好的選擇。須要注意的事若是你使用 mod_php5,就必須使用 prefork MPM。

若是你追求高性能和高穩定性,能夠爲 Apache 選擇與 nginx 相似的的 FPM 系統 worker MPM 或者 event MPM,它們分別使用 mod_fastcgi 和 mod_fcgid。這種方式能夠更高效的利用內存,運行速度也更快,可是配置也相對複雜一些。

共享主機

PHP 很是流行,不多有服務器沒有安裝 PHP 的,於是有不少共享主機,不過須要注意服務器上的 PHP 是不是最新穩定 版本。共享主機容許多個開發者把本身的網站部署在上面,這樣的好處是費用很是便宜,壞處是你不知道將和哪些 網站共享主機,所以須要仔細考慮機器負載和安全問題。若是項目預算容許的話,避免使用共享主機是上策。

構建及部署應用

若是你在手動的進行數據庫結構的修改或者在更新文件前手動運行測試,請三思然後行!由於隨着每個額外的手動任務的添加都須要去部署一個新的版本到應用程序,這些更改會增長程序潛在的致命錯誤。即便你是在處理一個簡單的更新,全面的構建處理或者持續集成策略,構建自動化絕對是你的朋友。

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

  • 依賴管理
  • 靜態資源編譯、壓縮
  • 執行測試
  • 文檔生成
  • 打包
  • 部署

構建自動化工具

構建工具能夠認爲是一系列的腳原本完成應用部署的通用任務。構建工具並不屬於應用的一部分,它獨立於應用層 ‘以外’。

如今已有不少開源的工具來幫助你完成構建自動化,一些是用 PHP 編寫,有一些不是。應該根據你的實際項目來選擇最適合的工具,不要讓語言阻礙了你使用這些工具,以下有一些例子:

Phing 是一種在 PHP 領域中最簡單的開始自動化部署的方式。經過 Phing 你能夠控制打包,部署或者測試,只須要一個簡單的 XML 構建文件。Phing (基於Apache Ant) 提供了在安裝或者升級 web 應用時的一套豐富的任務腳本,而且能夠經過 PHP 編寫額外的任務腳原本擴展。

Capistrano 是一個爲 中高級程序員 準備的系統,以一種結構化、可複用的方式在一臺或多臺遠程機器上執行命令。對於部署 Ruby on Rails 的應用,它提供了預約義的配置,不過也能夠用它來 部署 PHP 應用 。若是要成功的使用 Capistrano ,須要必定的 Ruby 和 Rake 的知識。

對 Capistrano 感興趣的 PHP 開發者能夠閱讀 Dave Gardner 的博文 PHP Deployment with Capistrano ,來做爲一個很好的開始。

Chef 不只僅只是一個部署框架, 它是一個基於 Ruby 的強大的系統集成框架,除了部署你的應用以外,還能夠構建整個服務環境或者虛擬機。

Deployer 是一個用 PHP 編寫的部署工具,它很簡單且實用。並行執行任務,原子化部署,在多臺服務器之間保持一致性。爲 Symfony、Laravel、Zend Framework 和 Yii 提供了通用的任務腳本。

適用於 PHP 開發者的 Chef 資源:

延伸閱讀:

持續集成

持續集成是一種軟件開發實踐,團隊的成員常常用來集成他們的工做, 一般每個成員至少天天都會進行集成 — 所以天天都會有許多的集成。許多團隊發現這種方式會顯著地下降集成問題, 並容許一個團隊更快的開發軟件。

– Martin Fowler

對於 PHP 來講,有許多的方式來實現持續集成。近來 Travis CI 在持續集成上作的很棒,對於小項目來講也能夠很好的使用。Travis CI 是一個託管的持續集成服務用於開源社區。它能夠和 Github 很好的集成,而且提供了不少語言的支持包括 PHP 。

延伸閱讀:

Back to Top

虛擬化技術

在開發和線上階段使用不一樣的系統運行環境的話, 常常會遇到各類各樣的 BUG, 而且在團隊開發的時候, 讓全部成員都保持使用最新版本的軟件和類庫, 也是一件很讓人頭痛的事情.

若是你是在 Windows 下開發, 線上環境是 Linux (或者別的非 Windows 系統) 的話, 或者團隊協同開發的時候, 建議使用虛擬機.

除了你們熟知的 VMware 和 VirtualBox 外, 還有不少工具可讓你快速, 輕鬆的用上虛擬環境.

Vagrant 簡介

Vagrant 可讓你使用單一的配置信息來部署一套虛擬環境, 最後打包爲一個所謂的 box (就是已經部署好環境的虛擬機器). 你能夠手動來安裝和配置 box, 也能夠使用自動部署工具, 如 Puppet 或者Chef .

自動部署工具可讓你快速部署一套如出一轍的環境, 避免了一大堆的手動的命令輸入, 而且容許你隨時刪除和重建一個全新的 box, 虛擬機的管理變得更加簡單.

Vagrant 還能夠在虛擬機和主機上分享文件夾, 意味着你能夠在主機裏面編輯代碼, 而後在虛擬機裏面運行.

須要更多的幫助?

下面是一些其餘的軟件, 能夠幫助你更好的使用 Vagrant:

  • Rove: 使用 Chef 自動化安裝一些經常使用的軟件, PHP 包含在內.
  • Puphpet: 簡單的 Web 圖形界面用來生成部署 PHP 環境的 Puppet 腳本, 此項目不只能夠用在開發上, 也能夠在生產環境中使用.
  • Protobox: 是一個基於 vagrant 的一個層, 還有 Web 圖形界面, 容許你使用一個 YAML 文件來安裝和配置虛擬機裏面的軟件.
  • Phansible: 提供了一個簡單的 Web 圖形界面, 用來建立 Ansible 自動化部署腳本, 專門爲 PHP 項目定製.

Docker 簡介

除了 Vagrant, Docker 是另外一個實現生產和開發環境統一的很是棒的方案.

Docker 爲各類應用程序提供了 Linux 容器.

你能夠安裝 Docker 鏡像, 如 MySQL 和 PostgreSQL 等, 而且不會污染到你的本地機器, 能夠看下 Docker Hub Registry, 在這裏你能夠找到你想要的, 提早配置好的, 容許你簡單幾部就能運行起來的 Linux 容器.

例子: 在 Docker 裏面運行 PHP 應用

在你成功 安裝 Docker 後, 你只須要一步就能夠安裝 Apache + PHP.

下面的命令, 會下載一個功能齊全的 Apache 和 最新版本的 PHP, 並會設置 WEB 目錄 /path/to/your/php/files 運行在 http://localhost:8080:

docker run -d --name my-php-webserver -p 8080:80 -v /path/to/your/php/files:/var/www/html/ php:apache

 

在使用 docker run 命令之後, 若是你想中止, 或者再次開啓容器的話, 只須要執行如下命令:

docker stop my-php-webserver
docker start my-php-webserver

 

瞭解更多關於 Docker 的信息

The commands mentioned above only show a quick way to run an Apache web server with PHP support but there are a lot more things that you can do with Docker.

上面的命令能讓你輕鬆使用 Apache + PHP 環境, 然而, Docker 還提供了好多別的命令, 例如, 做爲 PHP 程序員, 一個最重要的事情, 是讓你的 Web Server 和數據庫連接起來, 怎麼實現能夠仔細看下Docker User Guide.

Back to Top

緩存

PHP 自己來講是很是快的,可是但你當發起遠程鏈接、加載文件等操做時也會遇到瓶頸。 幸運的是,有各類各樣的工具能夠用來加速你應用程序某些耗時的部分,或者說減小某些耗時任務所須要運行的次數。

Opcode 緩存

當一個 PHP 文件被解釋執行的時候,首先是被編譯成名爲 opcode 的中間代碼,而後才被底層的虛擬機執行。 若是PHP文件沒有被修改過,opcode 始終是同樣的。這就意味着編譯步驟白白浪費了 CPU 的資源。

此時 opcode 緩存就派上用場了。經過將 opcode 緩存在內存中,它能防止冗餘的編譯步驟,而且在下次調用執行時獲得重用。設置 opcode 緩存只須要幾分鐘的時間,你的應用程序便會所以大大加速,實在沒有理由不用它。

PHP 5.5 中自帶了 opcode 緩存工具,叫作OPcache,早期的版本也能經過必定的配置使用它。 更多關於 opcode 緩存的資料: * OPcache (built-in since PHP 5.5) * APC (PHP 5.4 and earlier) *XCache * Zend Optimizer+ (part of Zend Server package) * WinCache (extension for MS Windows Server) * list of PHP accelerators on Wikipedia

對象緩存

有時緩存代碼中的單個對象會頗有用,好比有些須要很大開銷獲取的數據或者一些結果集不怎麼變化的數據庫查詢。你能夠使用一些緩存軟件將這些數據存放在內存中以便下次高速獲取。若是你得到數據後把他們存起來,下次請求直接從緩存裏面獲取數據,在減小數據庫負載的同時能極大提升性能。

許多流行的字節碼緩存方案也能緩存定製化的數據,因此更有理由好好使用它們了。APCu、XCache 以及 WinCache 都提供了 API,以便你將數據緩存到內存中

最經常使用的內存對象緩存系統是 APCu 和 Memcached 。APCu 對於對象緩存來講是個很好的選擇,它提供了簡單的 API 讓你能將數據緩存到內存,而且很容易設置和使用。APCu 的侷限性表如今它依賴於所在的服務器。另外一方面,Memcached 以獨立的服務的形式安裝,能夠經過網絡交互,這意味着你能將數據集中存在一個高速存取的地方,並且許多不一樣的系統能從中獲取數據。

值得注意的是當你以 CGI(FastCGI) 的形式使用 PHP 時,每一個進程將會有各自的緩存,好比說,APCu 緩存數據沒法在多個工做進程中共享。在這種狀況下,你可能得考慮 Memcached 了,因爲它獨立於 PHP 進程。

一般 APCu 在存取速度上比 Memcached 更快,可是 Memcached 在擴展上更有優點。若是你不但願應用程序涉及多個服務器,或者不須要 Memcached 提供的其餘特性,那麼 APCu 多是最好的選擇。

使用 APCu 的例子:

<?php
// check if there is data saved as 'expensive_data' in cache
$data = apc_fetch('expensive_data');
if ($data === false) {
    // data is not in cache; save result of expensive call for later use
    apc_add('expensive_data', $data = get_expensive_data());
}
print_r($data);

 

注意在 PHP 5.5 以前,APC 同時提供了對象緩存與字節碼緩存。APCu 是爲了將 APC 的對象緩存移植到 PHP 5.5+ 的一個項目,由於如今 PHP 有了內建的字節碼緩存方案 (OPcache)。

更多關於緩存系統的項目:

Back to Top

文檔撰寫

PHPDoc

PHPDoc 是註釋 PHP 代碼的非正式標準。它有許多不一樣的標記能夠使用。完整的標記列表和範例能夠查看 PHPDoc 指南

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

<?php
/**
 * @author A Name <a.name@example.com>
 * @link http://www.phpdoc.org/docs/latest/index.html
 */
class DateTimeHelper
{
    /**
     * @param mixed $anything Anything that we can convert to a \DateTime object
     *
     * @throws \InvalidArgumentException
     *
     * @return \DateTime
     */
    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標記, @author 標記是用來說明代碼的做者,在多位開發者的狀況下,能夠同時列出好幾位。其次 @link 標記用來提供網站連接,進一步說明代碼和網站之間的關係。

在這個類中,第一個方法的 @param 標記,說明類型、名字和傳入方法的參數。此外,@return 和 @throws 標記說明返回類型以及可能拋出的異常。

第2、第三個方法很是相似,和第一個方法同樣使用一個 @param 標記。第2、和第三個方法之間關鍵差別在註釋區塊使用/排除 @return 標記。@return void 標記明確告訴咱們沒有返回值,而過去省略 @return void 聲明也具備相同效果(沒有返回任何值)。

Back to Top

資源

Back to Top

相關文章
相關標籤/搜索