截至目前(2014.2), PHP 的最新穩定版本是 PHP5.5, 但有差不多一半的用戶仍在使用已經不在維護 [注] 的 PHP5.2, 其餘的一半用戶在使用 PHP5.3 [注].
因爲 PHP 那“集百家之長”的蛋疼語法,加上社區氛圍不好,很多人對新版本,新特徵並無興趣。
本文將會介紹自 PHP5.2 起,直至 PHP5.6 中增加的新特徵。
- PHP5.2 以前:autoload, PDO 和 MySQLi, 類型約束
- PHP5.2:JSON 支持
- PHP5.3:棄用的功能,匿名函數,新增魔術方法,命名空間,後期靜態綁定,Heredoc 和 Nowdoc, const, 三元運算符,Phar
- PHP5.4:Short Open Tag, 數組簡寫形式,Traits, 內置 Web 服務器,細節修改
- PHP5.5:yield, list() 用於 foreach, 細節修改
- PHP5.6: 常量增強,可變函數參數,命名空間增強
注:http://w3techs.com/technologies/details/pl-php/5/all
PHP5.2以前
(2006前)順便介紹一下 PHP5.2 已經出現但值得介紹的特徵。
autoload
大家可能都知道 __autoload() 函數,如果定義了該函數,那麼當在代碼中使用一個未定義的類的時候,該函數就會被調用,你可以在該函數中加載相應的類實現文件,如:
- function __autoload($classname) {
- require_once("{$classname}.php")
- }
- spl_autoload_register(function($classname)
- {
- require_once("{$classname}.php")
- });
注:SPL: Standard PHP Library, 標準 PHP 庫, 被設計用來解決一些經典問題(如數據結構).
PDO 和 MySQLi
即 PHP Data Object, PHP 數據對象,這是 PHP 的新式數據庫訪問接口。按照傳統的風格,訪問 MySQL 數據庫應該是這樣子:
- // 連接到服務器,選擇數據庫
- $conn = mysql_connect("localhost", "user", "password");
- mysql_select_db("database");
- // 執行 SQL 查詢
- $type = $_POST['type'];
- $sql = "SELECT * FROM `table` WHERE `type` = {$type}";
- $result = mysql_query($sql);
- // 打印結果
- while($row = mysql_fetch_array($result, MYSQL_ASSOC))
- {
- foreach($row as $k => $v)
- print "{$k}: {$v}\n";
- }
- // 釋放結果集,關閉連接
- mysql_free_result($result);
- mysql_close($conn);
除此之外,PDO 還提供了更多功能,比如:
•面向對象風格的接口
•SQL預編譯(prepare), 佔位符語法
•更高的執行效率,作爲官方推薦,有特別的性能優化
•支持大部分SQL數據庫,更換數據庫無需改動代碼
上面的代碼用 PDO 實現將會是這樣:
- // 連接到數據庫
- $conn = new PDO("mysql:host=localhost;dbname=database", "user", "password");
- // 預編譯SQL, 綁定參數
- $query = $conn->prepare("SELECT * FROM `table` WHERE `type` = :type");
- $query->bindParam("type", $_POST['type']);
- // 執行查詢並打印結果
- foreach($query->execute() as $row)
- {
- foreach($row as $k => $v)
- print "{$k}: {$v}\n";
- }
但如果你需要使用 MySQL 所特有的高級功能,那麼你可能需要嘗試一下 MySQLi, 因爲 PDO 爲了能夠同時在多種數據庫上使用,不會包含那些 MySQL 獨有的功能。
MySQLi 是 MySQL 的增強接口,同時提供面向過程和麪向對象接口,也是目前推薦的 MySQL 驅動,舊的C風格 MySQL 接口將會在今後被默認關閉。
MySQLi 的用法和以上兩段代碼相比,沒有太多新概念,在此不再給出示例,可以參見 PHP 官網文檔 [注]。
注:http://www.php.net/manual/en/mysqli.quickstart.php
類型約束
通過類型約束可以限制參數的類型,不過這一機制並不完善,目前僅適用於類和 callable(可執行類型) 以及 array(數組), 不適用於 string 和 int.
- // 限制第一個參數爲 MyClass, 第二個參數爲可執行類型,第三個參數爲數組
- function MyFunction(MyClass $a, callable $b, array $c)
- {
- // ...
- }
PHP5.2
(2006-2011)JSON 支持
包括 json_encode(), json_decode() 等函數,JSON 算是在 Web 領域非常常用的數據交換格式,可以被 JS 直接支持,JSON 實際上是 JS 語法的一部分。JSON 系列函數,可以將 PHP 中的數組結構與 JSON 字符串進行轉換:
- $array = array("key" => "value", "array" => array(1, 2, 3, 4));
- $json = json_encode($array);
- echo "{$json}\n";
- $object = json_decode($json);
- print_r($object);
- {"key":"value","array":[1,2,3,4]}
- stdClass Object
- (
- [key] => value
- [array] => Array
- (
- [0] => 1
- [1] => 2
- [2] => 3
- [3] => 4
- )
- )
PHP5.3
(2009-2012)PHP5.3 算是一個非常大的更新,新增了大量新特徵,同時也做了一些不向下兼容的修改。
棄用的功能
以下幾個功能被棄用,若在配置文件中啓用,則 PHP 會在運行時發出警告。
Register Globals
這是 php.ini 中的一個選項(register_globals), 開啓後會將所有表單變量($_GET和$_POST)註冊爲全局變量.看下面的例子:
- if(isAuth())
- $authorized = true;
- if($authorized)
- include("page.php");
但由於並沒有事先把 $authorized 初始化爲 false, 當 register_globals 打開時,可能訪問 /auth.php?authorized=1 來定義該變量值,繞過身份驗證。
該特徵屬於歷史遺留問題,在 PHP4.2 中被默認關閉,在 PHP5.4 中被移除。
Magic Quotes
對應 php.ini 中的選項 magic_quotes_gpc, 這個特徵同樣屬於歷史遺留問題,已經在 PHP5.4 中移除。該特徵會將所有用戶輸入進行轉義,這看上去不錯,在第一章我們提到過要對用戶輸入進行轉義。
但是 PHP 並不知道哪些輸入會進入 SQL , 哪些輸入會進入 Shell, 哪些輸入會被顯示爲 HTML, 所以很多時候這種轉義會引起混亂。
Safe Mode
很多虛擬主機提供商使用 Safe Mode 來隔離多個用戶,但 Safe Mode 存在諸多問題,例如某些擴展並不按照 Safe Mode 來進行權限控制。PHP官方推薦使用操作系統的機制來進行權限隔離,讓Web服務器以不同的用戶權限來運行PHP解釋器,請參見第一章中的最小權限原則.
匿名函數
也叫閉包(Closures), 經常被用來臨時性地創建一個無名函數,用於回調函數等用途。
- $func = function($arg)
- {
- print $arg;
- };
- $func("Hello World");
可以看到定義匿名函數依舊使用 function 關鍵字,只不過省略了函數名,直接是參數列表。
然後我們又調用了 $func 所儲存的匿名函數。
匿名函數還可以用 use 關鍵字來捕捉外部變量:
- function arrayPlus($array, $num)
- {
- array_walk($array, function(&$v) use($num){
- $v += $num;
- });
- }
在 arrayPlus() 的實現中,我們使用了 array_walk() 函數,它會爲一個數組的每一項執行一個回調函數,即我們定義的匿名函數。
在匿名函數的參數列表後,我們用 use 關鍵字將匿名函數外的 $num 捕捉到了函數內,以便知道到底應該加上多少。
魔術方法:__invoke(), __callStatic()
PHP 的面向對象體系中,提供了若干“魔術方法”,用於實現類似其他語言中的“重載”,如在訪問不存在的屬性、方法時觸發某個魔術方法。隨着匿名函數的加入,PHP 引入了一個新的魔術方法 __invoke().
該魔術方法會在將一個對象作爲函數調用時被調用:
- class A
- {
- public function __invoke($str)
- {
- print "A::__invoke(): {$str}";
- }
- }
- $a = new A;
- $a("Hello World");
- A::__invoke(): Hello World
命名空間
PHP的命名空間有着前無古人後無來者的無比蛋疼的語法:
- <?php
- // 命名空間的分隔符是反斜槓,該聲明語句必須在文件第一行。
- // 命名空間中可以包含任意代碼,但只有 **類, 函數, 常量** 受命名空間影響。
- namespace XXOO\Test;
- // 該類的完整限定名是 \XXOO\Test\A , 其中第一個反斜槓表示全局命名空間。
- class A{}
- // 你還可以在已經文件中定義第二個命名空間,接下來的代碼將都位於 \Other\Test2 .
- namespace Other\Test2;
- // 實例化來自其他命名空間的對象:
- $a = new \XXOO\Test\A;
- class B{}
- // 你還可以用花括號定義第三個命名空間
- namespace Other {
- // 實例化來自子命名空間的對象:
- $b = new Test2\B;
- // 導入來自其他命名空間的名稱,並重命名,
- // 注意只能導入類,不能用於函數和常量。
- use \XXOO\Test\A as ClassA
- }
命名空間時常和 autoload 一同使用,用於自動加載類實現文件:
- spl_autoload_register(
- function ($class) {
- spl_autoload(str_replace("\\", "/", $class));
- }
- );
這樣可以實現類定義文件分級儲存,按需自動加載。
注:http://www.php.net/manual/zh/language.namespaces.php
後期靜態綁定
PHP 的 OPP 機制,具有繼承和類似虛函數的功能,例如如下的代碼:
- class A
- {
- public function callFuncXXOO()
- {
- print $this->funcXXOO();
- }
- public function funcXXOO()
- {
- return "A::funcXXOO()";
- }
- }
- class B extends A
- {
- public function funcXXOO()
- {
- return "B::funcXXOO";
- }
- }
- $b = new B;
- $b->callFuncXXOO();
- B::funcXXOO
然而如果將所有函數都改爲靜態函數:
- class A
- {
- static public function callFuncXXOO()
- {
- print self::funcXXOO();
- }
- static public function funcXXOO()
- {
- return "A::funcXXOO()";
- }
- }
- class B extends A
- {
- static public function funcXXOO()
- {
- return "B::funcXXOO";
- }
- }
- $b = new B;
- $b->callFuncXXOO();
- A::funcXXOO()
- class A
- {
- static public function callFuncXXOO()
- {
- print static::funcXXOO();
- }
- // ...
- }
- // ...
- B::funcXXOO
Heredoc 和 Nowdoc
PHP5.3 對 Heredoc 以及 Nowdoc 進行了一些改進,它們都用於在 PHP 代碼中嵌入大段字符串。Heredoc 的行爲類似於一個雙引號字符串:
- $name = "MyName";
- echo <<< TEXT
- My name is "{$name}".
- TEXT;
就像雙引號字符串一樣,其中可以嵌入變量。
Heredoc 還可以用於函數參數,以及類成員初始化:
- var_dump(<<<EOD
- Hello World
- EOD
- );
- class A
- {
- const xx = <<< EOD
- Hello World
- EOD;
- public $oo = <<< EOD
- Hello World
- EOD;
- }
- $name = "MyName";
- echo <<< 'TEXT'
- My name is "{$name}".
- TEXT;
- My name is "{$name}".
用 const 定義常量
PHP5.3 起同時支持在全局命名空間和類中使用 const 定義常量。舊式風格:
- define("XOOO", "Value");
- const XXOO = "Value";
- // 正確
- const XXOO = 1234;
- // 錯誤
- const XXOO = 2 * 617;
三元運算符簡寫形式
舊式風格:
- echo $a ? $a : "No Value";
- echo $a ?: "No Value";
Phar
Phar即PHP Archive, 起初只是Pear中的一個庫而已,後來在PHP5.3被重新編寫成C擴展並內置到 PHP 中。Phar用來將多個 .php 腳本打包(也可以打包其他文件)成一個 .phar 的壓縮文件(通常是ZIP格式)。
目的在於模仿 Java 的 .jar, 不對,目的是爲了讓發佈PHP應用程序更加方便。同時還提供了數字簽名驗證等功能。
.phar 文件可以像 .php 文件一樣,被PHP引擎解釋執行,同時你還可以寫出這樣的代碼來包含(require) .phar 中的代碼:
- require("xxoo.phar");
- require("phar://xxoo.phar/xo/ox.php");
注:http://www.php.net/manual/zh/phar.using.intro.php
PHP5.4
(2012-2013)Short Open Tag
Short Open Tag 自 PHP5.4 起總是可用。在這裏集中講一下有關 PHP 起止標籤的問題。即:
- <?php
- // Code...
- ?>
- <? /* Code... */ ?>
- <?php echo $xxoo;?>
- <?= $xxoo;?>
使用這種簡寫形式在 HTML 中嵌入 PHP 變量將會非常方便。
對於純 PHP 文件(如類實現文件), PHP 官方建議頂格寫起始標記,同時 省略 結束標記。
這樣可以確保整個 PHP 文件都是 PHP 代碼,沒有任何輸出,否則當你包含該文件後,設置 Header 和 Cookie 時會遇到一些麻煩 [注].
注:Header 和 Cookie 必須在輸出任何內容之前被髮送。
數組簡寫形式
這是非常方便的一項特徵!
- // 原來的數組寫法
- $arr = array("key" => "value", "key2" => "value2");
- // 簡寫形式
- $arr = ["key" => "value", "key2" => "value2"];
Traits
所謂Traits就是“構件”,是用來替代繼承的一種機制。PHP中無法進行多重繼承,但一個類可以包含多個Traits.
- // Traits不能被單獨實例化,只能被類所包含
- trait SayWorld
- {
- public function sayHello()
- {
- echo 'World!';
- }
- }
- class MyHelloWorld
- {
- // 將SayWorld中的成員包含進來
- use SayWorld;
- }
- $xxoo = new MyHelloWorld();
- // sayHello() 函數是來自 SayWorld 構件的
- $xxoo->sayHello();
Traits中也同樣可以包含Traits. 篇幅有限不能逐個舉例,詳情參見官網 [注].
注:http://www.php.net/manual/zh/language.oop5.traits.php
內置 Web 服務器
PHP從5.4開始內置一個輕量級的Web服務器,不支持併發,定位是用於開發和調試環境。在開發環境使用它的確非常方便。
- php -S localhost:8000
其中localhost是監聽的ip,8000是監聽的端口,可以自行修改。
很多應用中,都會進行URL重寫,所以PHP提供了一個設置路由腳本的功能:
- php -S localhost:8000 index.php
你還可以使用 XDebug 來進行斷點調試。
細節修改
PHP5.4 新增了動態訪問靜態方法的方式:
- $func = "funcXXOO";
- A::{$func}();
- (new MyClass)->xxoo();
- print func()[0];
PHP5.5
(2013起)yield
yield關鍵字用於當函數需要返回一個迭代器的時候, 逐個返回值。
- function number10()
- {
- for($i = 1; $i <= 10; $i += 1)
- yield $i;
- }
list() 用於 foreach
可以用 list() 在 foreach 中解析嵌套的數組:
- $array = [
- [1, 2, 3],
- [4, 5, 6],
- ];
- foreach ($array as list($a, $b, $c))
- echo "{$a} {$b} {$c}\n";
- 1 2 3
- 4 5 6
細節修改
不推薦使用 mysql 函數,推薦使用 PDO 或 MySQLi, 參見前文。不再支持Windows XP.
可用 MyClass::class 取到一個類的完整限定名(包括命名空間)。
empty() 支持表達式作爲參數。
try-catch 結構新增 finally 塊。
PHP5.6
更好的常量
定義常量時允許使用之前定義的常量進行計算:
- const A = 2;
- const B = A + 1;
- class C
- {
- const STR = "hello";
- const STR2 = self::STR + ", world";
- }
- function func($arg = C::STR2)
更好的可變函數參數
用於代替 func_get_args()
- function add(...$args)
- {
- $result = 0;
- foreach($args as $arg)
- $result += $arg;
- return $result;
- }
- $arr = [2, 3];
- add(1, ...$arr);
- // 結果爲 6
命名空間
命名空間支持常量和函數:
- namespace Name\Space {
- const FOO = 42;
- function f() { echo __FUNCTION__."\n"; }
- }
- namespace {
- use const Name\Space\FOO;
- use function Name\Space\f;
- echo FOO."\n";
- f();
- }