# 2. 儲存與取出資料 {%hackmd QnyEFBdERZebn4iQDXNPnA %} ## 本章重點:如何儲存與取出資料 ## 01 儲存資料以供之後使用 - 兩種方式儲存資料 - 存入一般檔案 - 存入資料庫 ## 02 處理檔案三步驟 - 寫入檔案 - 開啟檔案(檔案若不存在,使用者需要建立它) - 資料寫入 - 關閉檔案 - 讀取資料 - 開啟檔案 - 資料讀取 - 關閉檔案 ## 03 開啟檔案 - 使用fopen => 稱為檔案模式。 ### 選擇檔案模式 - Why需要選擇檔案模式? => 提供一個**機制**讓作業系統(OS)**決定**如何處理其他人或腳本的**存取請求** - 作業系統需要知道你希望對檔案做哪些事情。包括: - 你開啟檔案時,其他腳本可否開啟相同檔案? - 你是否有權限使用該檔案? - 開啟檔案時,須做三個選擇 1. 讀取?寫入?讀取且寫入? 2. 如果選擇寫入。 - 若檔案不存在 - 覆蓋既有資料? - 還是附加新資料? - 若檔案存在? - 終止程式,而不是覆寫檔案。 3. 指明格式。二進位或文字檔? - fopen() => 提供以上三種選項的組合 ### 使用fopen()來開啟檔案 - 寫入檔案 ```php= $document_root = $SERVER['DOCUMENT_ROOT'] //這個變數會指向web伺服器的文件樹狀結構的底部 $fp = fopen("$document_root/../orders/orders.txt", 'w') //開啟檔案 //若在Windows環境,使用反斜線(\)來轉義 $fp = fopen("$document_root\\..\\orders\\orders.txt", 'w') //使用正斜線,可以在Windows與Unix機器互相搬移程式碼。不需要額外修改 ``` - fopen檔案模式摘要(+表示延續上一個的特性) - 可獨立使用 - 讀取 - r => 檔案開頭開始讀取 - r + => 讀取與寫入 - 寫入 - w => 開啟檔案寫入。若檔案存在,刪除既有;若不存在,新增 - w+ => 開啟檔案寫入與讀取 - 謹慎寫入 - x => 開啟檔案寫入。若檔案存在,它將不會開啟。fopen回傳警告。 - x+ => 開啟檔案寫入與讀取。 - 附加 - a => 開啟檔案,從尾部開始,只附加內容。若檔案不存在,新增 - a+ => 開啟檔案,從尾部開始,附加內容與讀取 - 無法獨立使用 - 二進位 - b => 與其中其他模式一同使用。若使用檔案系統會區分二進位與文字檔,建議使用本模式。(Windows會區分,Unix不會)。此為預設模式。 - 文字 - t => 只能在Windows系統中使用。 ### 處理開啟檔案時的問題 - 若無法開啟檔案,問自己: - 檔案是否存在? - 開啟者是否有檔案權限? - 如何讓自己更好理解檔案開啟失敗? ```php= @$fp = fopen("$document_root/../orders/orders.txt", 'ab') if(!$fp){ echo "<p><strong>Your order could not be processed at this time. Please try again.<strong></p>" } //@會告知PHP抑制這個函式產生的任何錯誤。當fopen()呼叫失敗時,函式會回傳false。它會讓PHP發出警告等級錯誤(E_WARNING) ``` ## 04 寫入檔案 - 三種寫入的函式 - fwrite() ```php= fwrite($fp, $outputstring); ``` - fputs() - file put string - 取代 fwrite()的函式 - file_put_contents => 會將data裡面的資料寫到名為filename的檔案 - 與file_get_contents成對 ### fwrite()的參數 - 可以接收三個參數 ```c= //fwrite()原型 int fwrite(resource handle, string [, int length]) //length => 是寫入的最大byte數。若想要以二進位模式寫入時,會用到第三個參數 //如何取得字串長度? //使用strlen() fwrite($fp, $outputstring, strlen($outputstring)) ``` ## 05 關閉檔案 - fclose($fp); - 成功時回傳true, 否則回傳false ## 06 讀取檔案內容 ### 開啟檔案來讀取:fopen() - 可以將檔案開成唯讀 ```php= $fp = fopen("../orders/orders.txt") ``` ### 知道何時停止:feof() - File end Of File - 使用while迴圈來讀取檔案,直到檔案結尾為止。 ```php= while (!feof($fp)) //意思是:如果不是end of file ,繼續讀取資料 ``` ### 讀取的方式分四種 1. 一次讀一行 2. 讀取整個檔案 3. 讀取字元 4. 讀取任意長度 ### 一次讀取一行:總共三種方式 - fgets() ```php= $order = fgets($fp); ``` - fgetss() ```c= //原型 string fgetss(resource fp [, int length[, string allowable_tags]]) //類似fgets,不過它會將所有包住字串的PHP與HTML標籤移除 ``` - fgetcsv() => 可以將檔案裡面的各行分開 ```php= $order = fgetcsv($fp, 0, "\t") //這段程式會從檔案中取出一段文字,並按照tab做切割 ``` ### 讀取整個檔案:總共四種 - readfile() => 可以直接從檔案讀取資料 ```php= readfile("document_root/../orders/orders.txt"); ``` - fpassthru() ```php= $fp =fopen("document_root/../orders/orders.txt"); fpassthru($fp); ``` - file() => 與readfile()相同,不過它會將檔案轉成陣列 ```php= $filearray = file("document_root/../orders/orders.txt"); //注意函式在舊版的PHP不是二進位安全的(binary safe) ``` - file_get_contents() - 與readfile()幾乎相同,不過它會以字串回傳檔案內容 ### 讀取字元 - fgetc() ```php= while(!feof($fp)){ $char = fgetc($fp); if (!feof($fp)){ echo ($char == "\n"? "<br /> : $char"); } } //fgetc()會回傳EOF字元,fgets()不會。因此當讀取完字元之後,必須再測試feof(),因為並不希望EOF字元回應到瀏覽器上。 //將\n轉換成<br /> => 為了清理格式 ``` ### fread() =>讀取任意數量的byte ```c= //原型 string fread(resource fp, int length); ``` ## 07 使用其他檔案函式 ### 檢查檔案是否存在:file_exists() ```php= if (file_exists("$document_root/../orders/oreders.txt")){ echo 'There are orders waiting to be preocessed.' }else{ echo 'There are currently no orders' } ``` ### 確認檔案大小:filesize() ```php= echo filesize("document_root/../orders/orders.txt"); //可以與fread()一同使用,讀取部分或全部檔案(更精準控制讀取byte個數) $fp = fopen("document_root/../orders/orders.txt"); echo n12br(fread($fp, filesize("document_root/../orders/orders.txt"))); fclose($fp); //n12br可以\n轉換成HTML的換行符號(<br />) ``` ### 刪除檔案:unlink() - 如果想要在處理訂單後刪除就檔案,可以使用unlink()(PHP沒有delete函式) ```php= unlink("document_root/../orders/orders.txt"); ``` ### 巡覽檔案內容 => 操作與尋找檔案指標位置 - rewind() => 會將檔案指標**重置**到檔案的開頭。 - 等效於呼叫fseek()並將位移量設置為0 - fseek() => 會將檔案指標**設置**到檔案中的某個位置 ```c= int fseek (resource fp, int offset [, int whence]) //fp => 檔案指標 //whence => 起始點 //offset => 位移量 fseek($fp, 100, 0) //表示將檔案指標fp移到從0(whence)算起的位移量100(offset)的地方。 ``` - ftell() => 會以 bytes**回報**指標在檔案中**前進多遠**了 ## 08 鎖定檔案 - flock() => file lock ```c= bool flock(resource fp, int operation [, int &wouldblock]) flock($fp, LOCK_EX); ``` - 設想情境:大流量網站中,有兩位顧客同時訂購商品,因此同時呼叫fopen(),並同時寫入。請問會發生什麼事?第二個訂單會出現在第一個訂單後面?抑或相反?會是相互交錯? - 答案是端看作業系統的運作邏輯 - flock的operation - LOCK_SH - 讀取鎖,可共用 - LOCK_EX - 寫入鎖,互斥(不可共用) - LOCK_UN - 解開既有的鎖 - LOCK_NB - 要求鎖定時,防止阻塞(Windows不支援) - 雖變得比較穩定,但仍非完美。舉例來說,若有兩個腳本同時要求鎖定,會如何?可能會產生競爭條件、甚至爭相鎖定,因而引發引發更多問題。透過資料庫,可以改善此問題。 ## 09 更好的方式:使用資料庫 ### 使用一般檔案的問題 - 檔案變大時,存取速度變很慢 - 很難搜尋特定的紀錄或群組。舉例來說,若要找住在某個地方的所有顧客,逼需要一個一個搜尋,浪費時間。 - 同時存取會是個問題。 - 很難中間插入或刪除資料(很難隨機存取) - 檔案權限劃分太簡陋。 ### RDBMS(**R**elational **D**ata**b**ase **M**anagement **S**ystem)如何解決這個問題? - 存取速度快 - 查詢特定條件方便 - RDBMS有內建機制可處理並取存取 - RDBMS可隨機存取資料 - RDBMS有內建權限系統