---
# System prepended metadata

title: 2. 儲存與取出資料

---

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