PHP 自 5.2 到 7.2 中新增的功能詳解
我也加了一些更仔細的修改和新增。
截至目前(2014.10), PHP 的最新穩定版本是 PHP5.6, 但有差不多一半的使用者仍在使用已經不在維護 [注] 的 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 伺服器,SESSION讀取檔案上傳進度,細節修改
- PHP5.5:yield, list() 用於 foreach, empty()函式的引數支援任意資料,包括一個函式呼叫,直接對一個array和string取值。若干細節修改。
- PHP5.6: 常量增強,可變函式引數,名稱空間增強
PHP5.2以前
(2006前)
順便介紹一下 PHP5.2 已經出現但值得介紹的特徵。
autoload
大家可能都知道 __autoload() 函式,如果定義了該函式,那麼當在程式碼中使用一個未定義的類的時候,該函式就會被呼叫,你可以在該函式中載入相應的類實現檔案,如:
function __autoload($classname) { require_once("{$classname}.php") }
但該函式已經不被建議使用,原因是一個專案中僅能有一個這樣的 __autoload() 函式,因為 PHP 不允許函式重名。但當你使用一些類庫的時候,難免會出現多個 autoload 函式的需要,於是 spl_autoload_register() 取而代之:
spl_autoload_register(function($classname)
{
require_once("{$classname}.php")
});
spl_autoload_register() 會將一個函式註冊到 autoload 函式列表中,當出現未定義的類的時候,SPL [注] 會按照註冊的倒序逐個呼叫被註冊的 autoload 函式,這意味著你可以使用 spl_autoload_register() 註冊多個 autoload 函式.
注: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);
為了能夠讓程式碼實現資料庫無關,即一段程式碼同時適用於多種資料庫(例如以上程式碼僅僅適用於MySQL),PHP 官方設計了 PDO.
除此之外,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"; }
PDO 是官方推薦的,更為通用的資料庫訪問方式,如果你沒有特殊需求,那麼你最好學習和使用 PDO.
但如果你需要使用 MySQL 所特有的高階功能,那麼你可能需要嘗試一下 MySQLi, 因為 PDO 為了能夠同時在多種資料庫上使用,不會包含那些 MySQL 獨有的功能。
MySQLi 是 MySQL 的增強介面,同時提供面向過程和麵向物件介面,也是目前推薦的 MySQL 驅動,舊的C風格 MySQL 介面將會在今後被預設關閉。
MySQLi 的用法和以上兩段程式碼相比,沒有太多新概念,在此不再給出示例,可以參見 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 = ["key" => "value", "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 ) )
值得注意的是 json_decode() 預設會返回一個物件而非陣列,如果需要返回陣列需要將第二個引數設定為 true.
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 設定為 true. 然後根據 $authorized 的值來決定是否顯示頁面.
但由於並沒有事先把 $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");
以上程式碼定義了一個匿名函式,並賦值給了 $func.
可以看到定義匿名函式依舊使用 function 關鍵字,只不過省略了函式名,直接是引數列表。
然後我們又呼叫了 $func 所儲存的匿名函式。
匿名函式還可以用 use 關鍵字來捕捉外部變數:
function arrayPlus($array, $num) { array_walk($array, function(&$v) use($num){ $v += $num; }); }
上面的程式碼定義了一個 arrayPlus() 函式(這不是匿名函式), 它會將一個數組($array)中的每一項,加上一個指定的數字($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
__callStatic() 則會在呼叫一個不存在的靜態方法時被呼叫。
名稱空間
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)); } );
當你例項化一個類 \XXOO\Test\A 的時候,這個類的完整限定名會被傳遞給 autoload 函式,autoload 函式將類名中的名稱空間分隔符(反斜槓)替換為斜槓,幷包含對應檔案。
這樣可以實現類定義檔案分級儲存,按需自動載入。
後期靜態繫結
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
可以看到,當在 A 中使用 $this->funcXXOO()
時,體現了“虛擬函式”的機制,實際呼叫的是 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()
這是因為 self 的語義本來就是“當前類”,所以 PHP5.3 給 static 關鍵字賦予了一個新功能:後期靜態繫結:
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 以三個左尖括號開始,後面跟一個識別符號(TEXT), 直到一個同樣的頂格的識別符號(不能縮排)結束。
就像雙引號字串一樣,其中可以嵌入變數。
Heredoc 還可以用於函式引數,以及類成員初始化:
var_dump(<<<EOD
Hello World
EOD
);
class A
{
const xx = <<< EOD
Hello World
EOD;
public $oo = <<< EOD
Hello World
EOD;
}
Nowdoc 的行為像一個單引號字串,不能在其中嵌入變數,和 Heredoc 唯一的區別就是,三個左尖括號後的識別符號要以單引號括起來:
$name = "MyName"; echo <<< 'TEXT' My name is "{$name}". TEXT;
輸出:
My name is "{$name}".
用 const 定義常量
PHP5.3 起同時支援在全域性名稱空間和類中使用 const 定義常量。
舊式風格:
define("XOOO", "Value");
新式風格:
const XXOO = "Value";
const 形式僅適用於常量,不適用於執行時才能求值的表示式:
// 正確 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");
更多資訊請參見官網 [注].
PHP5.4
(2012-2013)
Short Open Tag
Short Open Tag 自 PHP5.4 起總是可用。
在這裡集中講一下有關 PHP 起止標籤的問題。即:
<?php // Code... ?>
通常就是上面的形式,除此之外還有一種簡寫形式:
<? /* Code... */ ?>
還可以把
<?php echo $xxoo;?>
簡寫成:
<?= $xxoo;?>
這種簡寫形式被稱為 Short Open Tag, 在 PHP5.3 起被預設開啟,在 PHP5.4 起總是可用。
使用這種簡寫形式在 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, 解決衝突,修改訪問許可權,為函式設定別名等等。
Traits中也同樣可以包含Traits. 篇幅有限不能逐個舉例,詳情參見官網 [注].
內建 Web 伺服器
PHP從5.4開始內建一個輕量級的Web伺服器,不支援併發,定位是用於開發和除錯環境。
在開發環境使用它的確非常方便。
php -S localhost:8000
這樣就在當前目錄建立起了一個Web伺服器,你可以通過 http://localhost:8000/ 來訪問。
其中localhost是監聽的ip,8000是監聽的埠,可以自行修改。
很多應用中,都會進行URL重寫,所以PHP提供了一個設定路由指令碼的功能:
php -S localhost:8000 index.php
這樣一來,所有的請求都會由index.php來處理。
你還可以使用 XDebug 來進行斷點除錯。
SESSION 擴充套件現在能追蹤檔案的上傳進度
<form action="upload.php" method="POST" enctype="multipart/form-data"> <input type="hidden" name="<?php echo ini_get("session.upload_progress.name"); ?>" value="123" /> <input type="file" name="file1" /> <input type="file" name="file2" /> <input type="submit" /> </form>
$_SESSION["upload_progress_123"] = array( "start_time" => 1234567890, // 請求時間 "content_length" => 57343257, // 上傳檔案總大小 "bytes_processed" => 453489, // 已經處理的大小 "done" => false, // 當所有上傳處理完成後為TRUE "files" => array( 0 => array( "field_name" => "file1", // 表單中上傳框的名字 // The following 3 elements equals those in $_FILES "name" => "foo.avi", "tmp_name" => "/tmp/phpxxxxxx", "error" => 0, "done" => true, // 當這個檔案處理完成後會變成TRUE "start_time" => 1234567890, // 這個檔案開始處理時間 "bytes_processed" => 57343250, // 這個檔案已經處理的大小 ), // An other file, not finished uploading, in the same request 1 => array( "field_name" => "file2", "name" => "bar.avi", "tmp_name" => NULL, "error" => 0, "done" => false, "start_time" => 1234567899, "bytes_processed" => 54554, ), ) );
細節修改
PHP5.4 新增了動態訪問靜態方法的方式:
$func = "fu