雲端教學課程


PDO - MySQL
PHP Data Objects




先到的同學先把WAMP安裝起來。

為什麼要使用PDO ?



現在鼓勵用 PDO 連 MySQL,主要的原因是 mysql_* 
已經被 PHP 5.5+ 宣告為 deprecated:

還有 escape 的原因,mysql_escape_string() 不知道
MySQL 連線的 charset,對於 escape 會有影響。

 PDO 提供了相似的界面,對於開發者比較友善

在開始之前



  • 打開localhost/phpmyadmin
  • 設定資料庫編碼 utf8_unicode
  • 建立blog資料庫及使用者
  • 新增message資料表 ( id,時間,留言者,留言內容 )
  • 在message資料表打上測試資料

連接資料庫

config.php
<?php$config['db']['dsn']='mysql:host=localhost;dbname=blog;charset=utf8';$config['db']['user'] = 'blog';$config['db']['password'] = '';
$db = new PDO(        $config['db']['dsn'],        $config['db']['user'] ,        $config['db']['password'],        array(           PDO::ATTR_EMULATE_PREPARES => false,           PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION           )        );

Try catch

message.php
<?phprequire 'config.php';

function some_logging_function($log){ echo 'LOG : ' . $log . '<br />';}
try { $db->query('hi'); //錯誤的查詢字串} catch(PDOException $ex) { echo "有錯誤發生!<br />"; //使用者訊息 some_logging_function($ex->getMessage());
}

查詢資料


<?php

function getData($db) { $stmt = $db->query("SELECT * FROM message"); return $stmt->fetchAll(PDO::FETCH_ASSOC);//回傳全部資料 } //then much later try { $data = getData($db); var_dump($data);//印出資料} catch(PDOException $ex) {
some_logging_function($ex->getMessage()); }

查詢資料 - foreach

<?php //model$data = getData($db);
<!--View--><table>
  <thead>     <tr>       <th>序號</th><th>時間</th><th>留言者</th><th>內容</th>     </tr>   </thead>   <tbody>    <?php foreach( $data as $message ){ ?>     <tr>       <td><?php echo $message['message_id'];?></td>       <td><?php echo $message['message_time'];?></td>       <td><?php echo $message['messager'];?></td>       <td><?php echo $message['message'];?></td>     </tr>    <?php } ?>   </tbody> </table>

資訊安全 - XSS、CSRF攻擊

記得把輸出的資料過濾,才不會在留言裡被塞程式碼。
攻擊示範 : 把這輸入到留言裡按送出。
<script type="text/javascript">
  alert('HI!這是XSS攻擊!');
  location = 'http://163.17.136.249';
</script>
防護方法 : strip_tags( ) 或 htmlspecialchars( )
<?php 
$str = "<a href=\"blah.htm\">Text</a>"; 

echo strip_tags($str);
// Text //http://www.php.net/manual/en/function.strip-tags.php
echo htmlspecialchars($str); 
// &lt;a href=\&quot;blah.htm\&quot;&gt;Text&lt;/a&gt;
//http://www.php.net/manual/en/function.htmlspecialchars.php

修改後的輸出

把不是由系統產生的都加上過濾。

<tbody>  <?php foreach( $data as $message ){ ?>
  <tr>
    <td><?php echo $message['message_id'];?></td>
    <td><?php echo $message['message_time'];?></td>
    <td><?php echo htmlspecialchars($message['messager']);?></td>
    <td><?php echo htmlspecialchars($message['message']);?></td>
  </tr>  <?php } ?>
</tbody>

查詢資料 - rowCount()

Model
<?php
function getData($db) {
  $stmt = $db->query("SELECT * FROM message");
  return array(
          'rows' => $stmt->fetchAll(PDO::FETCH_ASSOC),
          'rowCount' => $stmt->rowCount()//回傳資料數
          );
}
View
<table>
  <caption>總共<?php echo $data['rowCount'];?>筆留言</caption>
  <thead>
    ...
  </thead>
  <tbody>
    ...
  </tbody>
</table>

新增資料 - lastInsertId()

message_add.php
<?phprequire 'config.php';

$result = $db->exec("INSERT INTO message" .            "(`message_time`, `messager` ,`message`)" .             "VAULES(NOW(),'John', 'This is a message')");$insertId = $db->lastInsertId();echo '新增成功,id=' . $insertId;

SQL要自己打才會發現錯誤哦 ^_<

資訊安全 - SQL Injection攻擊

  • 進到SQL的參數都要過濾過,嚴重資料庫都能被刪掉。
  • mysql_*時會用mysql_real_escape_string()過濾,
    但會有編碼問題。
  • 在PDO時我們會用prepare方法來過濾。

<?phprequire 'config.php';

$messager = $_POST['messager'];
$message = $_POST['message'];
$result = $db->exec("INSERT INTO message" .             "(`message_time`, `messager` ,`message`)" .             "VAULES(NOW(),'$messager', '$message')");

帶參數 - Preparing

<?phprequire 'config.php';

$messager = '鬼才';
$message = '測試資料123';
$stmt = $db->prepare("INSERT INTO message" .             "(`message_time`, `messager` ,`message`)" .             "VAULES(NOW(),:messager, :message)"); $stmt->execute(array(             ':messager' => $messager,             ':message' => $message             ));$insertId = $db->lastInsertId();
echo '新增成功,id=' . $insertId;
回去看 message.php 有沒新資料進去了!

整理成函式

<?phprequire 'config.php';

function new_message( $db ,$messager ,$message ){  $stmt = $db->prepare("INSERT INTO message" .               "(`message_time`, `messager` ,`message`)" .               "VAULES(NOW(),:messager, :message)");   $stmt->execute(array(               ':messager' => $messager,               ':message' => $message               ));  return $db->lastInsertId();
}
echo '新增成功,id=' . new_message( $db ,'鬼才' ,'測試ABC');

新增表單

message.php
<form action="message_add.php" method="post">
  <label for="messager">留言者</label>   <input type="text" name="messager" id="messager" />   <br />   <label for="message">留言內容</label>   <textarea name="message" id="message"></textarea>   <br />   <input type="submit" />   <input type="reset" /> </form>
message_add.php 
<?php echo '新增成功,id=' . new_message( $db ,$_POST['messager'] ,$_POST['message']);

處理資料 - affected_rows

message_delete.php
<?php
require 'config.php';

function delete_user_message( $db ,$messager ,$message ){
  $stmt = $db->prepare("UPDATE `message` SET " . 
               "`message` = :message " . 
               "WHERE `messager` = :messager");
  $stmt->execute(array(
               ':messager' => $messager,
               ':message' => $message
               ));
  return $stmt->rowCount();
}

echo '成功刪除' . delete_user_message($db,'鬼才','這是垃圾訊息') . '筆資料。';

處理資料 - 刪除特定留言 - 1

message.php
<!--View-->
<form action="message_delete.php" method="post">
  <button type="submit">確定刪除</button>
  <table>
    ...
    <tbody>
      <?php foreach( $data['rows'] as $message ){ ?>
      <tr>
        <td>          <label>
            <input type="checkbox" name="message_id[]" value="<?php echo $message['message_id'];?>"  />
            <?php echo $message['message_id'];?>
          </label>        </td>
        ...
      </tr>
      <?php } ?>
     </tbody>
  </table>
</form>

處理資料 - 刪除特定留言 - 2

message_delete.php -1
<?php //model...
function delete_message( $db ,$message_id ,$message ){
  try{
    $stmt = $db->prepare("UPDATE `message` SET " . 
                 "`message` = :message " . 
                 "WHERE `message_id` = :message_id");    foreach($message_id as $id){
      $stmt->execute(array(	
                   ':message_id' => $id,
                   ':message' => $message
                   ));
    }
    return 1;	
  } catch(PDOException $ex) {
    return 0;
  }
}

處理資料 - 刪除特定留言 - 3

message_delete.php -2
<?php //controllerif ( isset($_POST['messager']) ){
  echo '成功刪除' . delete_user_message($db, $_POST['messager'], '這是垃圾訊息。') . '筆資料。';
} else if ( isset($_POST['message_id']) ){   if(delete_message($db, $_POST['message_id'], '這是垃圾訊息。')){     echo '成功刪除';   }   else{     echo '失敗';   } } else{   echo '參數錯誤。'; }

處理資料 - 刪除特定留言 - 4

message.php
<form action="message_delete.php" method="post">
  <label for="messager">使用者</label>
  <input type="text" value="" name="messager" id="messager" />
  <input type="submit" value="刪除特定使用者留言"  />
</form>

同樣是POST到message_delete.php,
但是POST的參數不同,處理的function也不同。

  • POST message_id 會刪除指定的留言。
  • POST messager 會刪除指定使用者的留言。

交易 - Transactions

如果其中一條SQL錯誤,就把剛執行過的都還原回去。
 <?php
try {
    $db->beginTransaction();
 
    $db->exec("SOME QUERY");
 
    $stmt = $db->prepare("SOME OTHER QUERY?");
    $stmt->execute(array($value));
 
    $stmt = $db->prepare("YET ANOTHER QUERY??");
    $stmt->execute(array($value2, $value3));
 
    $db->commit();} catch(PDOException $ex) {
    //Something went wrong rollback!
    $db->rollBack();
    echo $ex->getMessage();
}

回家作業

  1. 會員註冊登入,每個會員一個留言版。
  2. 會員密碼用sha1加密或md5加密。
  3. 管理員身份刪除留言。
  4. 限制登入後才能留言。
  5. 研究MVC架構,
    下週會做出一個跟無名小站一樣的BLOG。

  題示 :   
  1. 每個會員一個留言版可以在message新增一個
    會員編號欄位。
  2. 會員登入功能另外建立一個會員資料表,
    用SESSION記錄登入狀態。
  3. 參考程式碼及MVC觀念在下面。

參考資料

判斷會員登入
 isset($_SESSION['user'])
會員登出 logout.php
 unset($_SESSION['user'])
判斷管理員 ( 使用者名稱 = 留言版版主名稱)
function is_admin( $message_board ,$_SESSION['user']['member'] ){  if( $message_board == $_SESSION['user']['member'] ){    return 1;  }  else{    return 0;  }}

參考資料

會員登入 login.php
<?php
function login( $db ,$member ,$password){
  $stmt = $db->prepare("SELECT * `member` WHERE " . 
               "`member` = :member " . 
               "AND `password` = :password");
  $stmt->execute(array(
               ':member' => $member,
               /*拿加密過的密碼去比對資料庫已加密的密碼*/
               ':password' => sha1($password)
               ));  if( $stmt->rowCount() == 1 ){//是否有符合的資料
    $user = $stmt->fetchAll(PDO::FETCH_ASSOC);
    $_SESSION['user'] = $user[0];//把會員資料寫進SESSION裡
    return 1;//登入成功
  }
  else{
    return 0;//登入失敗
  }	
}

參考資料

MVC方式

index.php?page=頁面名稱 [&board=會員名稱]

  • 頁面名稱 : 留言列表 、刪除留言、登入、登出、註冊。
  • 會員名稱 : 在留言列表時用來指定瀏覽的留言版。

  1. Controller判斷現在是在那個會員的留言版。
  2. 呼叫Model取回留言版內的留言。
  3. Model收到呼叫後,用SQL對資料庫操作再回傳。
  4. Controller收到回傳的資料,呼叫View來顯示。

參考資料

MVC概念


PDO

By jackai

PDO

  • 8,836