# 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有內建權限系統