:::

4. 表單函數與資料庫存取

一、表單函數

WebDeveloper 附加元件:https://chrispederick.com/work/web-developer/

  1. index.php 新增表單函數:
    // 表單函數
    function post_form()
    {
        global $content, $smarty;
        // 加入預設值
        $content = [
            'title'      => '',
            'directions' => '',
            'end'        => date("Y-m-d", strtotime("+10 day")),
            'priority'   => '中',
            'assign'     => [],
            'done'       => 0,
        ];
    }
  2. post_form.tpl 加入預設值
    • <input type="格式" name="名稱" value="預設值" placeholder="佔位字元"> ,如:
      <input type="" class="form-control" id="title" name="title" placeholder="待辦事項" value="{$content.title}" />
    • 大量文字框:<textarea name="名稱">預設值</textarea> ,如:
      <textarea class="form-control" id="directions" name="directions" rows="3">{$content.directions}</textarea>
    • 下拉選單:<select name="名稱" size=1>選項</select> ,如:
      <select class="form-select" aria-label="priority" name="priority" id="priority">
        <option value="高" {if $content.priority=='高'}selected{/if}>高</option>
        <option value="中" {if $content.priority=='中'}selected{/if}>中</option>
        <option value="低" {if $content.priority=='低'}selected{/if}>低</option>
      </select>
    • 單選框:<input type="radio" name="名稱"  value="值 1">選項文字 1,如:
      <div class="form-check form-check-inline">
        <input class="form-check-input" type="radio" id="done1" name="done" value="1" {if $content.done=='1'}checked{/if} />
        <label class="form-check-label" for="done">是</label>
      </div>
    • 複選框:<input type="checkbox" name="名稱[]"  value="值 1">選項文字 1,如:
      <div class="form-check form-check-inline">
        <input class="form-check-input" type="checkbox" id="assign1" name="assign[]" value="李大頭"  {if "李大頭"|in_array:$content.assign}checked="checked"{/if} />
        <label class="form-check-label" for="assign">李大頭</label>
      </div>
    • 隱藏框與送出表單
      // 表單函數
      function post_form()
      {
          global $content,$smarty;
          // 加入預設值
          $content = [
              /**略*/
          ];
          $next_op = 'add';
      
          $smarty->assign('next_op', $next_op);
      }    
      <div class="text-center">
        <input type="hidden" name="op" value="{$next_op}">
        <input type="submit" name="send" value="儲存" class="btn btn-primary" />
      </div>

二、PHP的變數過濾

  1. 外來變數
    • 用post方法傳過來的,我們用 $_POST['變數名稱'] 接收。
    • 用get方法傳過來的,我們用 $_GET['變數名稱'] 接收。
    • 用 $_REQUEST['變數名稱'] 同時可接收來自 post、get 和 cookie 的變數 。
  2. 過濾外來變數
    • 外來變數通常來自使用者輸入或者比較容易竄改,所以,凡是外面傳來的變數,一律要進行變數過濾。
    • 一般讀出文字可用內建的 htmlspecialchars() 函數來過濾。
      • 直接用 htmlspecialchars($string) 的話,預設只轉化雙引號,不對單引號做轉義,所以,這樣用htmlspecialchars($string,ENT_QUOTES) 更好。
    • 亦可利用PHP內建的 filter_var() 函數來過濾變數。
      • 幾種常用過濾方法,完整過濾器可由此查看
        名稱 功用
        FILTER_CALLBACK option可以讓開發者用自訂的function處理
        FILTER_SANITIZE_STRING 去除標籤或特殊字元(html標籤會直接被消除)
        FILTER_SANITIZE_ENCODED 與urlencode()相同,過濾特殊字串
        FILTER_SANITIZE_MAGIC_QUOTES 過濾針對SQL injection做過濾(例如單、雙引號)
        FILTER_SANITIZE_SPECIAL_CHARS 針對HTML做encoding,例如<會轉成&lt;
        FILTER_SANITIZE_EMAIL 過濾e-mail,刪除e-mail格式不該出現的字元(除了$-_.+!*'{}|^~[]`#%/?@&=和數字),例如a(b)@gmail.com會被過濾成ab@gmail.com
        FILTER_SANITIZE_URL 過濾URL,刪除URL格式不該出現的字元
        FILTER_SANITIZE_NUMBER_INT 刪除所有字元,只留下數字與+-符號
        FILTER_SANITIZE_NUMBER_FLOAT 刪除所有字元,只留下數字和+-.,eE
        FILTER_VALIDATE_INT 判斷數字是否有在範圍內
        FILTER_VALIDATE_BOOLEAN 判斷布林值,1、true、on、yes都會判斷成true,反之為false,若是這些以外的值會回傳NULL
        FILTER_VALIDATE_FLOAT 判斷是否為浮點數
        FILTER_VALIDATE_REGEXP 利用regexp做驗證
        FILTER_VALIDATE_URL URL驗證
        FILTER_VALIDATE_EMAIL e-mail驗證
        FILTER_VALIDATE_IP IP驗證
      • $op= isset($_REQUEST['op']) ? filter_var($_REQUEST['op'], FILTER_SANITIZE_SPECIAL_CHARS) : "";
      • filter_var($string, FILTER_SANITIZE_SPECIAL_CHARS) 換行麻煩,因此若要輸出HTML的換行文字過濾仍建議採用 htmlspecialchars($string, ENT_QUOTES); 對$smarty樣板輸出有較好支援性 {$content.directions|nl2br}
    • 過濾數字: 數字的過濾,只要加個(int)即可,如$sn=(int)$_GET['sn'];
    • 不同過濾器的運用時機:
      • 一般有大量文字夾雜HTML語法用 htmlspecialchars($string,ENT_QUOTES)
      • 要在php檔案中運算用的,請用PHP來過濾 filter_var($string, FILTER_SANITIZE_SPECIAL_CHARS)
      • 要寫入資料庫的,用資料庫的real_escape_string()來過濾。

三、寫入資料到資料庫

  1. 要操作MySQL,必須用SQL語言,新增資料的SQL語法如下(大小寫無關):
    INSERT [INTO] `資料表名稱` [(`欄位1`, `欄位2`...)] VALUES ('值1', '值2'...)
  2. 建議凡是資料庫名稱、資料表名稱、欄位名稱都用重音符號`包起來。
  3. 凡是「值」的,都用單引號'包起來。
  4. 過濾變數: 利用 $db->real_escape_string() 過濾資料,目的是順利讓所有資料存入資料庫,並避免隱碼攻擊。
    $title  = $db->real_escape_string($_POST['title']);
  5. $db->query($sql) 就是送執行指令到資料庫。
  6. $db->error 會秀出資料庫傳回來的錯誤訊息。
  7. 取得寫入時該資料產生的流水號:$db->insert_id
  8. 程式碼:
    //新增清單
    function add()
    {
        global $db;
        //過濾變數
        $title      = $db->real_escape_string($_POST['title']);
        $directions = $db->real_escape_string($_POST['directions']);
        $end        = $db->real_escape_string($_POST['end']);
        $priority   = $db->real_escape_string($_POST['priority']);
        $assign     = $db->real_escape_string(implode(';', $_POST['assign']));
        $done       = intval($_POST['done']);
    
        // 連線資料庫
        $sql = "INSERT INTO `list` ( `title`, `directions`, `end`, `priority`, `assign`, `done`,`create_time`,`update_time`)
        VALUES ('{$title}', '{$directions}', '{$end}', '{$priority}', '{$assign}', '{$done}',now(),now())";
    
        if (!$db->query($sql)) {
            throw new Exception($db->error);
        }
    
        $sn = $db->insert_id;
        return $sn;
    }

四、寫入後須轉向

  1. 凡是寫入、修改、刪除,進行完都應該做轉向,避免使用者重新整理畫面,又重複執行寫入、修改或刪除:header("location: index.php?sn={$sn}");
  2. header()函數基本上是設定文件檔頭,其中 location屬性可以指定文件轉向,故利用它來達成轉向功能。
  3. 程式碼:siwtch
    switch ($op) {
        case 'add':
            $sn = add();
            // 無提示訊息
            // header("location: index.php?sn={$sn}");
            // exit;
            // 有提示訊息
            echo "<script>alert('新增成功');location.href='index.php?sn={$sn}';</script>;";
            exit;

五、從資料庫中讀取資料

  1. 讀取資料庫的內容,一律用 select 語法:
    SELECT `查詢的欄位` [FROM `資料表名稱` 附加的篩選條件]
  2. 其中篩選條件語法如下:
    [where 篩選條件]
    [group by `欄位名稱`][having group的篩選條件]
    [order by {unsigned_integer | `欄位名稱` | formula} [asc | desc] ,...]
    [limit [起點,] 筆數]
  3. 有順序關係,需注意。

六、 從資料庫取得資料的方法

  1. 寫SQL送去資料庫執行後,會傳回一個資源變數物件,如$result
  2. 可以利用$result的各種取得資料方法,將資料一筆一筆取回。
  3. 一筆資料以上的資料,請放至while(){}迴圈中取回。
  4. 利用 $result->fetch_assoc() 取出的資料陣列,會以資料表欄位名稱為陣列索引; 以$result->fetch_row() 取出的資料陣列,是以欄位順序為陣列索引,通常搭配list()使用。
  5. 練習$data=$result->fetch_assoc();
  6. 練習$data=$result->fetch_row(); 
  7. 搭配 list($sn, $title, $directions,$end,$priority,$assign,$done, $create_time, $update_time)=$result->fetch_row(); 效果同 $data=$result->fetch_assoc();
  8. 完整程式碼:
    // 列出所有資料
    function list_all()
    {
        global $db, $smarty, $content;
        $sql    = "select * from `list` order by end";
        $result = $db->query($sql);
    
        if (!$result) {
            throw new Exception($db->error);
        }
    
        $content = [];
        // 法一
        // while ($data = $result->fetch_assoc()) {
        //     $content[] = $data;
        // }
        // 法二(樣板要改,不建議)
        // while ($data = $result->fetch_row()) {
        //     $content[] = $data;
        // }
        // 法三
        $i = 0;
        while (list($sn, $title, $directions, $end, $priority, $assign, $done, $create_time, $update_time) = $result->fetch_row()) {
    
            $content[$i]['sn']          = $sn;
            $content[$i]['title']       = $title;
            $content[$i]['directions']  = $directions;
            $content[$i]['end']         = $end;
            $content[$i]['priority']    = $priority;
            $content[$i]['assign']      = $assign;
            $content[$i]['done']        = $done;
            $content[$i]['create_time'] = $create_time;
            $content[$i]['update_time'] = $update_time;
            $i++;
        }
    }
    

七、幾個常用的迴圈用法

  1. 不曉得迴圈數的
    while(條件為真){
      //執行動作
    }
  2. 已知道迴圈數
    for($i=0;$i<迴圈數;$i++){
      //執行動作
    }
  3. 抽取陣列
    foreach($陣列 as $索引=>$值){
      //執行動作
    }

八、 讀出資料的安全性過濾

  1.   利用PHP的 filter_var 過濾器 FILTER_SANITIZE_SPECIAL_CHARS:同htmlspecialchars() 來過濾字串。
    //過濾變數
    $title      = filter_var($title, FILTER_SANITIZE_SPECIAL_CHARS);
    $directions = htmlspecialchars($directions, ENT_QUOTES);
    $priority   = filter_var($priority, FILTER_SANITIZE_SPECIAL_CHARS);
  2. 若是所見及所得編輯器,如CKEDITOR不需要過濾。