PHP 統一資源處理 API——流的概述與使用詳解
在現代PHP 特性中,流或許是最出色但使用率最低的。雖然 PHP 4.3 就引入了流,但是很多開發者並不知道流的存在,因為人們很少提及流,而且流的文檔也很匱乏。PHP 官方文檔對流的解釋如下:
流的作用是提供統一的公共函數來處理文件、網路和數據壓縮等操作。簡單而言,流是具有流式行為的資源對象,也就是說,流可以線性讀寫,並且可以通過 fseek() 之類的函數定位到流中的任何位置。
可能看完這段解釋後還是雲里霧裡,我們簡化一下,流的作用是在出發地和目的地之間傳輸數據。出發地和目的地可以是文件、命令行進程、網路連接、ZIP 或 TAR 壓縮文件、臨時內存、標準輸入或輸出,或者是通過 PHP 流封裝協議實現的任何其他資源。
如果你讀寫過文件,就用過流;如果你從 讀取過數據,或者把輸入寫入 ,也用過流。流為 PHP 的很多 IO 函數提供了底層實現,如 、 、 和 等。PHP 的流函數提供了不同資源的統一介面。
我們可以把流比作管道,把水(資源數據)從一個地方引到另一個地方。在水從出發地到目的地的過程中,我們可以過濾水,可以改變水質,可以添加水,也可以排出水。
流封裝協議
流式數據的種類各異,每種類型需要獨特的協議,以便讀寫數據,我們稱這些協議為 流封裝協議 。例如,我們可以讀寫文件系統,可以通過 HTTP、HTTPS 或 SSH 與遠程 Web 伺服器通信,還可以打開並讀寫 ZIP、RAR 或 PHAR 壓縮文件。這些通信方式都包含下述相同的過程:
開始通信
讀取數據
寫入數據
結束通信
雖然過程是一樣的,但是讀寫文件系統中文件的方式與收發 HTTP 消息的方式有所不同,流封裝協議的作用是使用通用的介面封裝這種差異。
每個流都有一個協議和一個目標。指定協議和目標的方法是使用流標識符: ,其中 是流的封裝協議, 是流的數據源。
http://流封裝協議
下面使用 HTTP 流封裝協議創建了一個與 Flicker API 通信的 PHP 流:
$json = file_get_contents(
"http://api.flickr.com/services/feeds/photos_public.gne?format=json");
不要以為這是普通的網頁 URL, 函數的字元串參數其實是一個流標識符。 協議會讓 PHP 使用 HTTP 流封裝協議,在這個參數中, 之後是流的目標。
註:很多 PHP 開發者可能並不知道普通的 URL 其實是 PHP 流封裝協議標識符的偽裝。
file://流封裝協議
我們通常使用 、 、 和 等函數讀寫文件系統,因為 PHP 默認使用的流封裝協議是 ,所以我們很少認為這些函數使用的是 PHP 流。下面的示例演示了使用 流封裝協議創建一個讀寫 文件的流:
$handle = fopen("file:///etc/hosts","rb");
while(feof($handle) !==TRUE) {
echofgets($handle);}fclose($handle);
我們通常會省略掉 協議,因為這是 PHP 使用的默認值。
php://流封裝協議
編寫命令行腳本的 PHP 開發者會感激 流封裝協議,這個流封裝協議的作用是與 PHP 腳本的標準輸入、標準輸出和標準錯誤文件描述符通信。我們可以使用 PHP 提供的文件系統函數打開、讀取或寫入下面四個流:
:這是個只讀 PHP 流,其中的數據來自標準輸入。PHP 腳本可以使用這個流接收命令行傳入腳本的信息;
:把數據寫入當前的輸出緩衝區,這個流只能寫,無法讀或定址;
:從系統內存中讀取數據,或者把數據寫入系統內存。缺點是系統內存有限,所有使用 更安全;
:和 類似,不過,沒有可用內存時,PHP 會把數據寫入這個臨時文件。
其他流封裝協議
PHP 和 PHP 擴展還提供了很多其他流封裝協議,例如,與 ZIP 和 TAR 壓縮文件、FTP 伺服器、數據壓縮庫、Amazon API、Dropbox API 等通信的流封裝協議。需要注意的是,PHP 中的 、 、 、 以及 等函數不僅可以用來處理文件系統中的文件,還可以在所有支持這些函數的流封裝協議中使用。
註:更多流封裝協議,請參考官方網站: http://php.net/manual/zh/wrappers.php
自定義流封裝協議
我們還可以自己編寫 PHP 流封裝協議。PHP 提供了一個示例 類,演示如何編寫自定義的流封裝協議,支持部分或全部 PHP 文件系統函數。關於如何編寫,具體請參考以下文檔:
http://php.net/manual/zh/class.streamwrapper.php
http://php.net/manual/zh/stream.streamwrapper.example-1.php
流上下文
有些 PHP 流能夠接受一系列可選的參數,這些參數叫流上下文,用於定製流的行為。不同的流封裝協議使用的流上下文有所不同,流上下文使用 函數創建,這個函數返回的上下文對象可以傳入大多數文件系統函數。
例如,你知道可以使用 file_get_contents() 發送 HTTP POST 請求嗎?使用一個流上下文對象即可實現:
$requestBody ="{"username":"nonfu"}";$context = stream_context_create([
"http"=> [
"method"=>"POST",
"header"=>"Content-Type: application/json;charset=utf-8;
Content-Length: ". mb_strlen($requestBody),
"content"=> $requestBody ]]);$response = file_get_contents("https://my-api.com/users",
false, $context);
流上下文是個關聯數組,最外層鍵是流封裝協議的名稱,流上下文數組中的值針對不同的流封裝協議有所不同,可用的設置參考各個 PHP 流封裝協議的文檔。
流 過濾器
目前為止我們討論了如何打開流,讀取流中的數據,以及把數據寫入流。不過,PHP 流真正強大的地方在於過濾、轉換、添加或刪除流中傳輸的數據,例如,我們可以打開一個流處理 Markdown 文件,在把文件內容讀入內存的過程中自動將其轉化為 HTML。
註:PHP 所有可用流過濾器請參考官方文檔: http://php.net/manual/zh/filters.php 。
若想把過濾器附加到現有的流上,要使用 函數,下面我們以 過濾器演示如何把文件中的內容轉換成大寫字母:
$handle = fopen("test.txt","rb");stream_filter_append($handle,"string.toupper");
while(feof($handle) !==true) { echo fgets($handle);}fclose($handle);
運行該腳本,輸出的都是大寫字母:
ABCDEEFGHIJKLMNHELLO LARAVELACADEMY!
我們還可以使用 流封裝協議把過濾器附加到流上,不過,使用這種方式之前必須先打開 PHP 流:
$handle = fopen("php://filter/read=
string.toupper/resource=test.txt","rb");
while(feof($handle) !==true) { echo fgets($handle);}fclose($handle);
這個方式實現效果和 函數一樣,但是相比之下更為繁瑣。不過,PHP 的某些文件系統函數在調用後無法附加過濾器,例如 和 ,使用這些函數時只能使用 流封裝協議附加流過濾器。
自定義流過濾器
我們還可以編寫自定義的流過濾器。其實,大多數情況下都要使用自定義的流過濾器,自定義的流過濾器是個 PHP 類,繼承內置的 類( http://php.net/manual/zh/class.php-user-filter.php ),且必須實現 、 和 方法,最後,必須使用 函數註冊自定義的流過濾器。
註:PHP 流會把數據分成按次序排列的桶,一個桶中盛放的流數據是固定的(如 4096 位元組),如果還用管道比喻,就是把水放在一個個水桶中,順著管道從出發地漂流到目的地,在漂流過程中會經過過濾器,過濾器一次可以接收並處理一個或多個桶,一定時間內過濾器接收到的桶叫做桶隊列。桶隊列中的每個桶對象都有兩個公共屬性: 和 ,分別表示桶的內容和長度。
下面我們自定義一個流過濾器 ,把流數據讀入內存時審查其中的髒字:
classDirtyWordsFilterextendsphp_user_filter{
/** *@paramresource $in 流入的桶隊列 *@paramresource $out 流出的桶隊列 *@paramint $consumed 處理的位元組數 *@parambool $closing 是否是流中最後一個桶隊列 *@returnint * 接收、處理再轉運桶中的流數據,在該方法中,
我們迭代桶隊列對象,把髒字替換成審查後的值 */publicfunctionfilter($in, $out, &$consumed, $closing){ $words = ["grime","dirt","grease"]; $wordData = [];
foreach($wordsas$word) { $replacement = array_fill(, mb_strlen($word),"*"); $wordData[$word] = implode("", $replacement); } $bad = array_keys($wordData); $good = array_values($wordData);
// 迭代桶隊列中的每個桶while($bucket = stream_bucket_make_writeable($in)) {
// 審查桶對象中的髒字$bucket->data = str_replace($bad, $good, $bucket->data);
// 增加已處理的數據量$consumed += $bucket->datalen;
// 把桶放入流向下游的隊列中stream_bucket_append($out, $bucket); }
returnPSFS_PASS_ON; }}
然後,我們必須使用 函數註冊這個自定義的 流過濾器:
stream_filter_register("dirty_words_filter","DirtyWordsFilter");
第一個參數用於標識這個自定義過濾器的過濾器名,第二個參數是這個自定義過濾器的類名。接下來就可以使用這個自定義的流過濾器了:
$handle = fopen("test.txt","rb");stream_filter_append($handle,"dirty_words_filter");
while(feof($handle) !==true) { echo fgets($handle);}fclose($handle);
修改 內容如下:
abcdeefghijklmnHello LaravelAcademy!grimeI hate dirty things!
運行上面的自定義過濾器腳本,結果如下:
abcdeefghijklmnHello LaravelAcademy!*****I hate****y things!
更多分享,敬請關注
本文來源網路,侵立刪!


※TP5驗證碼實現
※模仿KOA,用php來寫一個極簡的開發框架
TAG:PHP技術大全 |