天天看點

關于PHP流不得不說的那些事

關于PHP流不得不說的那些事

相信不少PHP開發者或多或少都見過類似于 "php://input" 或者 "php://output" 這樣的内容,很多人都知道這兩個的作用一個是接收的 POST 請求中的原始 body 内容,另一個其實和 echo 之類的輸出一樣是進行輸出的。當然,我們的文章内容不會如此的簡單,其實類似這樣的 php:// 開頭的協定還有好幾種,它們共同稱為 PHPIO流協定(PHP輸入/輸出流協定) 。

這種協定有什麼用呢?我們知道計算機中正常的協定有 http:// ,這是我們做web開發最熟悉的。還有 file:// 表示檔案,ftp:// 表示ftp協定,當然,還有一些不太常用的 zlib:// 、 data:// 、 rar:// ,等等,這些協定PHP都是支援的,而且這些協定都是約定俗成的并且有相應的檔案或流類型支援的協定。通過這些協定我們的程式可以讀取、解析這些協定所對應的相關内容。比如說http協定,伺服器、用戶端浏覽器都是因為支援了相同的http協定規範,是以才能夠通過這個協定來進行傳輸,而傳輸的内容是什麼呢?正是我們看到的網頁或接口文本。而今天我們的主角 php:// 協定,其實也有另一個别名是 PHP僞協定 。僞協定的原因其實就是這種協定隻是PHP自身所支援的并定義的一種協定,而且也僅僅隻是 IO 相關操作的一種協定規範。

好了,廢話就說到這裡,我們來一個一個的看看 php:// 相關的内容都有哪些。

stdin 輸入流

while ($line = fopen('php://stdin', 'r')) {
    $info = fgets($line);
    echo $info;
    if ($info == "exit\n") {
        break;
    }
}

while ($info = fgets(STDIN)) {
    echo $info;
    if ($info == "exit\n") {
        break;
    }
}      

上述代碼有什麼用呢?相信做過 C 或者 Java 開發的人會更有感覺,stdin 是擷取PHP程序腳本的輸入,也就是我們在使用指令行 php xxx.php 運作PHP腳本檔案時,擷取指令行輸入内容的。上述代碼就是使用 while 循環一直監聽指令行的輸入,當你輸入内容後進行列印,如果輸入的是 exit 就退出循環也就是結束腳本的運作。

這裡除了正常的用 fopen() 擷取 php://stdin 句柄外,還使用了另一種方式,也就是第二個循環所展示的 STDIN 常量來友善快捷地直接擷取輸入内容。這也是PHP所推薦的方式。同時,下面講的 php://stdout 和 php://stderr ,也有相應的 STDOUT 和 STDERR 常量。

stdout 、 stderr 和 output 輸出流

$stdout = fopen('php://stdout', 'w');
fputs($stdout, 'fopen:stdout');
echo PHP_EOL;
file_put_contents("php://stdout", "file_put_contents:stdout");
echo PHP_EOL;

file_put_contents("php://stderr", "file_put_contents:stderr");
echo PHP_EOL;

$output = fopen('php://output', 'w');
fputs($output, 'fopen:output');
echo PHP_EOL;
file_put_contents("php://output", "file_put_contents:output");
echo PHP_EOL;      

這三種都是輸出流,其實就和 echo 、 print 一樣,就是将内容列印輸出的。不過不同的地方在于,stdin 和 stdout 是針對PHP指令行的輸出。也就是說,如果我們是通過浏覽器檢視這個腳本的話,這兩個輸出的内容是不會列印到浏覽器上的。小夥伴們可以試試用 php -S localhost:8081 <測試檔案> 來測試下上述代碼,通路 http://localhost:8081 的話,浏覽器上會輸出 output 列印的内容,而指令行這邊則會列印 stdin 和 stdout 所輸出的内容。

另外需要注意的,這三個輸出流都是隻寫的,而 stdin 是隻讀的。也就是說 file_get_contents() 對這三個輸出流是沒什麼用的,而 file_put_contents() 對 stdin 流也是沒效果的。

input 通路請求的原始資料的隻讀流

這個相信做過接口開發的大多數人都會接觸過。目前端或用戶端使用 body raw 方式發送資料時,就使用這個協定來接收POST中的原始 body 内容。

echo file_get_contents("php://input");      

非常簡單,這裡我們直接使用 postman 來模拟這種請求,可以看我們是能夠正常接收到 body raw 裡面的内容的。見下圖:

關于PHP流不得不說的那些事

memory 、 temp 記憶體及臨時檔案流

$mem = fopen('php://memory', 'r+');
for ($i = 0; $i < 10; $i++) {
    fwrite($mem, 'fopen:memory');
}
rewind($mem);
while ($info = fgets($mem)) {
    echo $info, PHP_EOL;
}
fclose($mem);      

這兩個流協定是輸入、輸出都支援的,它們都是在記憶體中讀寫資料。不同的是, php://temp 會在資料超過一定容量時将資料寫到臨時檔案中。這裡我們就不示範 temp 的操作了,它和 memory 的操作代碼是非常像的。另外需要注意的,它們兩個操作都是一次性的,也就是說,如果我們在寫入(fwrite)後直接關閉(fclose)了句柄,那麼後面再讀取的話(fgets),是無法擷取到内容的。

filter 用于資料打開時的篩選過濾

readfile("php://filter/read=string.toupper/resource=http://www.baidu.com");
echo file_get_contents("php://filter/read=convert.base64-encode/resource=http://www.baidu.com");      

這個自己試試就知道它的好處了,第一行我們是擷取百度頁面的内容,并把内容中所有的字母替換成大寫字母了。第二個過濾器則是直接将百度首頁的内容轉成base64編碼的内容了,是不是非常強大,我覺得這個功能可以是我們好好開發的一個能力。

總結

其實說實話,筆者本人平常也就是用過 php://input 這一個協定而已,偶爾或者說基本一年難得用上幾次 stdin 來進行腳本調試,但是,這并不妨礙我們了解學習這些流協定的使用。最主要的是,通過學習後我們更進一步的了解了它們的作用及适用的場景,這樣就可以在将來需要的時候靈活使用。

測試代碼:

​​https://github.com/zhangyue0503/dev-blog/blob/master/php/202003/source/%E5%85%B3%E4%BA%8EPHP%E6%B5%81%E4%B8%8D%E5%BE%97%E4%B8%8D%E8%AF%B4%E7%9A%84%E9%82%A3%E4%BA%9B%E4%BA%8B.php​​