PHP7 入門研習

1. 寫在開始之前

  1. 有需求,才學得快。
  2. 每一行程式都有其用途及道理,為什麼要放這裡?務必搞懂之。
  3. 程式語法不必背,但一定要知道怎麼用,去哪裡找來用。
  4. 一樣的目的有千百種達成的方法,多試,多想,多做,多問。
  5. 歡迎提出任何問題,講師不是什麼都懂,懂的可以直接給建議,不懂的可以一起討論研究找對策。

PHP7 入門研習

1-1 安裝開發環境

一、以UniForm Server建立AMP環境

  1. UniForm Server優點是小巧強大,功能全模組化!日後升級很方便!
  2. UniServerZIII 伺服器環境上課用
    1. 若打算裝在C磁碟,請按右鍵「以系統管理員身份執行」來安裝該exe檔
    2. 預設會解壓縮到「磁碟代號:\UniServerZ」
    3. 請開啟伺服器的控制台:\UniServerZ\UniController.exe
    4. 資料的帳號為root,密碼為12345,若要修改,點擊控制台的「MySQL→Change MySQL password」即可修改MySQL密碼。
    5. 啟動資料庫 Start MySQL及網頁伺服器:Start Apache。
    6. 若有跳出防火牆訊息,一律選「是」
    7. 若無法啟動UniForm Server或者Apache無法啟動,請以管理員身份安裝 Visual Studio 2015 Visual C++ 可轉散發套件
    8. 若80port被佔:
      1.檢查是否有啟動IIS,請至「控制台→系統管理工具→IIS管理員」停止之即可。
      2.檢查是否有啟動Skype或迅雷,若有請先關閉之。
      3. 開啟命令提示字元視窗,輸入以下指令:
      netstat -ano
      找出佔用Port 80的程式PID? 倘若發現PID = 4,請下達以下指令:
      net stop http
      Sc config start=disabled

      這樣子問題應該就解決了。

二、Sublime Text 3 文字編輯器

  1. 開發工具,請使用自己慣用的文字編輯器即可,上課用Sublime官網)為主
  2. 下載解壓縮後,執行「sublime_text.exe」
  3. 若是 php.exe的位置不在「C:/UniServerZ/core/php70/php.exe」下,那必須調整以下幾個設定,將路徑修正為正確位置:
    1. 點選「設定→Package Settings→phpfmt→Settings User」
    2. 點選「設定→Package Settings→sublime Linter→Settings User」
  4. 設定環境變數(一些套件會用到)
    1. 直接開始選單的搜尋框輸入env,開啟「編輯您帳戶的環境變數」,或是開啟Windows的「控制台→系統及安全性→系統→進階系統設定」,並切換到「進階」頁籤,點選「環境變數」。
    2. 點擊下方的「Path」並且按下「編輯」。若沒有「Path」,就改按「新增」來新增一個環境變數。(若原設定的最後面沒有分號,請自行加入;)
      C:\UniServerZ\core\php70\
      

PHP7 入門研習

1-2 上課範例及需求

上課範例:活動報系統

系統需求:

  1. 只有特定人員,登入後才能發布活動
  2. 到了報名截止日後就不可再報名。
  3. 只有開設活動的人才能管理、刪除該活動,不可管理別人的。
  4. 刪除活動時,須將報名者資料也一併刪除。
  5. 報名者需登入使可報名,可以有取消報名或編輯報名資料的功能
  6. 報名完可以Email通知活動開設者及報名者。
  7. 點入活動時,開設者可以看到完整名單,訪客只能看到部份資訊(個資保護)

PHP7 入門研習

2. PHP基本語法

  1. PHP基本寫法:
    <?php
    echo "Hello World!";
  2. 請存到網頁目錄,如:C:\UniServerZ\www\index.php

  3. PHP程式的副檔名一律為.php

  4. PHP程式碼必須放在<?php和?>的起始標記和結束標記符號中。

  5. 若是該檔案中只有純粹的PHP語法,並沒有其他的HTML語法,那麼,就不建議加上「?>」符號

  6. echo是PHP用來輸出資料的語言結構(不是函數),常用。

  7. 在PHP中,凡是資料類型是字串的都必須用引號包起來,用雙引號或單引號都可以。

  8. 每一個PHP語句後要加上「;」結尾。

  9. 開啟瀏覽器,在網址列輸入「http://localhost/index.php」或「http://127.0.0.1/index.php」

PHP7 入門研習

2-1 註解

  1. 單行註解:單行註解以「//」或「#」開頭,後面接的文字就是註解文字,這種註解只能用在一行的文字上
    <?php
    echo "註解可以直接寫在後面<br>"; 	// 這是c++風格的單行註解
    // 這是c++風格的單行註解
    echo "當然註解寫在上面也行<br>"; 
    echo "這是另一種單行註解"; 		#這是Unix Shell風格的單行註解
  2. 多行註解:用「/*...*/」將註解文字包起來
    <?php
    /* 這是多行註解只要前後有兩
    個多行註解的符號包起來即可  */
    
    /* 
    這也是多行註解只要前後有兩
    個多行註解的符號包起來即可 
    */

PHP7 入門研習

2-2 PHP資訊頁

  1. 請建立phpinfo.php
    <?php
    phpinfo();
  2. 其中「Loaded Configuration File」就是目前您的PHP所使用的設定檔所在位置及檔名
  3. 常用設定值:
    設定項目 建議值

    date.timezone

    主機預設時區,若主機在台灣,請務必設置為「Asia/Taipei」,否則系統抓到的可能會有誤差。

    Asia/Taipei

    display_errors

    是否顯示錯誤訊息?建議開啟!!否則網站變成空白時將很難進行除錯。

    On

    file_uploads

    是否允許檔案上傳。需配合 upload_max_filesize, upload_tmp_dir, post_max_size 等設定。一般而言,上傳上限的設定,大小需求如下:memory_limit > post_max_size > upload_max_filesize

    On

    max_execution_time

    每個程序最大允許執行時間(秒),0 表示沒有限制。這個參數有助於阻止劣質程序無休止的佔用伺服器資源。

    150

    max_file_uploads

    最多只能傳幾個檔案?請視需求設定之。

    300

    max_input_time

    每個程序解析輸入資料的最大允許時間(秒)。
    -1 表示不限制。

    120

    max_input_vars

    可接收的變數數量,超過此數量,就無法完全接收表單內容。

    5000

    memory_limit

    一個程序所能夠申請到的記憶體空間 (可以使用 K 和 M 作為單位)。 這有助於防止劣質程序消耗完伺服器上的所有記憶體。如果要取消記憶體限制,則必須將其設為 -1 。

    240M

    post_max_size

    允許送出的 POST 表單大小。

    該值必須大於 upload_max_filesize 的值。
    如果啟用了記憶體限制,那麼該值應當小於 memory_limit 指令的值。

    220M

    upload_max_filesize

    允許上傳的檔案的最大尺寸。

    200M

PHP7 入門研習

2-3 建立專案目錄

如果未來會在網頁目錄下建立許多專案,那麼可以在網頁目錄下建立一個資料夾來放置專案程式的相關檔案,例如建立一個 signup 目錄。

如:C:\UniServerZ\www\signup

PHP7 入門研習

3. 套用Smarty樣板

  1. Smarty的官網在:http://www.smarty.net
  2. 下載 smarty 3.1.30 並解壓縮到網頁目錄 C:\UniServerZ\www 下
  3. 將解壓縮後的目錄名稱改為 smarty 就好。
  4. smarty目錄下,只要留下libs即可,其餘並不需要。
  5. 建立四個Smarty需要的目錄:
    • templates:放置原始樣板的目錄(一定會用到
    • templates_c:編譯後的樣板目錄(需可寫入
    • configs:設定目錄(不見得會用到)
    • cache:樣板快取目錄(需可寫入
  6. 看起來像這樣:
  7. 或直接下載整理好的 smarty 3.1.29 並解壓縮到網頁目錄 C:\UniServerZ\www 下即可。

PHP7 入門研習

3-1 使用 Smarty

一、Smarty基本操作

  1. 大原則:和外觀有關的東西都放到.html或.tpl中,所需要的資料全由.php提供,簡單範例,index.php內容:
    <?php
    require_once 'smarty/libs/Smarty.class.php';
    $smarty  = new Smarty;
    $name    = 'Tad';
    $smarty->assign('name', $name);
    $smarty->display('index.tpl');
    
  2. 樣板檔一律放至 templates 目錄中
  3. PHP檔中最常用的就是利用 $smarty->assign('樣板標籤名稱', $變數值); 將變數送至樣板檔。
  4. templates/index.tpl 內容:
    <h2>Hello {$name}</h2>
    

 二、Smarty變數及陣列

傳送內容 PHP檔(*.php) Smarty樣板檔(*.tpl)
一般變數
$name="tad";
$smarty->assign('name', $name);

 

{$name}

 

一維陣列
$user['name']="tad";
$user['birthday']="1973-06-16";
$smarty->assign('user', $user);

 

{$user.name} 的生日是 {$user.birthday}

 

二維陣列
$users[1]['name']="tad";
$users[1]['birthday']="1973-06-16";
$users[2]['name']="phebe";
$users[2]['birthday']="1973-03-10";
$smarty->assign('users', $users);

 

{foreach $users as $user}
  {$user.name} 的生日是{$user.birthday}
{/foreach}

或

{$user.1.name} 的生日是 {$user.1.birthday}
{$user.2.name} 的生日是 {$user.2.birthday}

 

三、 常用Smarty迴圈用法

  1. Smarty迴圈用來處理陣列,常用方法如下:
    {foreach $來源變數 as $別名}
      {$別名.索引}
    {foreachelse}
      該變數沒有值時要出現的內容
    {/foreach}
  2. 迴圈還有一些特別的用法:
    • {$別名@first} 迴圈第一圈
    • {$別名@last} 迴圈最後一圈
    • {$別名@index} 取得迴圈的索引值,依序輸出0、1、2......
    • {$別名@iteration} 取得迴圈的計數值,依序輸出1、2、3......
    • {$別名@total} 取得迴圈執行總數
  3. 詳情可見:http://www.smarty.net/docs/en/language.function.foreach.tpl

四、Smarty 其他常用用法

  1. 在樣板中,可以直接用 $samrty.get 來取得所有 $_GET 的變數陣列,同理若要在樣板中取得 $_SESSION,那就是用 $smarty.session
  2. 註解的寫法 {* 註解 *}
  3. 在樣板中,Smarty 也可以直接拿PHP的函數來用,我們稱之為「變數修飾器」,例如:
    {if $變數|in_array:$陣列}
  4. 變數後面要加上 |

  5. | 後面加上函數名稱,函數需要的參數用 : 格開

  6. 刪除快取:

    $smarty->clearAllCache();

     

PHP7 入門研習

4. 前端操作界面

沒時間慢慢搞的話,請直接下載整理好的頁面檔案

一、加入Bootstrap

  1. http://getbootstrap.com/getting-started/#download下載Bootstrap
  2. 解壓縮至class並將目錄改名為bootstrap
  3. 加入樣板中的方法:
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!--BootStrap-->
    <link rel="stylesheet" href="class/bootstrap/css/bootstrap.min.css">
  4. 另外js請加在</body>前即可,若沒用到bootstrap的外掛模組,其實不加也沒關係。
    <!--BootStrap js-->
    <script src="class/bootstrap/js/bootstrap.min.js"></script>

二、加入 jQuery

  1. https://jquery.com/download/下載 jquery 以及 jQuery Migrate (請直接另新檔至class即可)
  2. 可將版本號拿掉,避免日後升級還得改程式。
  3. 加入樣板中的方法:
    <!--jQuery-->
    <script src="class/jquery.min.js"></script>
    <script src="class/jquery-migrate.min.js"></script>

三、加入 jQuery-ui

  1. 下載jquery-ui https://jqueryui.com/resources/download/jquery-ui-1.12.1.zip
  2. 解壓縮至class並將目錄改名為 jquery-ui
  3. 加入樣板中的方法:
    <!--jQuery UI-->
    <link rel="stylesheet" href="class/jquery-ui/jquery-ui.min.css">
    <script src="class/jquery-ui/jquery-ui.min.js"></script>

四、加入 Font Awesome

  1. http://fontawesome.io/下載 Fort Awesome
  2. 解壓縮至class並將目錄改名為font-awesome
  3. 加入樣板中的方法:
    <!--Font awesome-->
    <link rel="stylesheet" href="class/font-awesome/css/font-awesome.min.css">

五、基本樣板

<!DOCTYPE html>
<html lang="zh-Hant">

<head>
  <title>頁面</title>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <!--BootStrap-->
  <link rel="stylesheet" href="class/bootstrap/css/bootstrap.min.css">
  <!--jQuery-->
  <script src="class/jquery.min.js"></script>
  <script src="class/jquery-migrate.min.js"></script>
<!--jQuery UI-->
<link rel="stylesheet" href="class/jquery-ui/jquery-ui.min.css">
<script src="class/jquery-ui/jquery-ui.min.js"></script>
  <!--Font awesome-->
  <link rel="stylesheet" href="class/font-awesome/css/font-awesome.min.css">
</head>

<body>
  主內容
  <!--BootStrap js-->
  <script src="class/bootstrap/js/bootstrap.min.js"></script>
</body>

</html>

 

PHP7 入門研習

4-1 BootStrap 基礎排版

  1. BootStrap 是一個市占率高的 CSS 框架,用來快速美化網頁用。
  2. 在此講義中,只要看到 class="" 幾乎都是用來套用 BootStrap 用的。
  3. BootStrap 將版面分成12欄,左 9 右 3 的兩欄式界面:
    <div class="container">
      <h1 class="page-header">活動報名系統</h1>
      <div class="row">
        <div class="col-md-9">主內容區</div>
        <div class="col-md-3">側邊欄</div>
      </div>
    </div>
    
  4. class="container" 是 BootStrap 有限寬度的頁面容器,最大寬度1170px
  5. class="container-fluid" 是 BootStrap 滿版的頁面容器
  6. 一個頁面可以有好幾個容器。
  7. class="row" 則是一個橫向區域,裡面分為12欄
  8. BootStrap將螢幕依解析度分成四種:
    螢幕解析度<768px,class語法為col-xs ,如手機。
    螢幕解析度≥768px,class語法為col-sm,如平板。
    螢幕解析度≥992px,class語法為col-md,如桌機螢幕。
    螢幕解析度≥1200px,class語法為col-lg,如桌機大螢幕 。
  9. class=" col-md-9 " 代表螢幕解析度≥992px 時「主內容區」佔了9欄
  10. 左右若想交換,就把3、4行對調一下即可。
  11. page-header 就只是一個頁面標題樣式

PHP7 入門研習

4-2 設計登入面板

  1. 請在側邊欄中加入BootStrap面板語法
    <div class="panel panel-danger">
      <div class="panel-heading">登入</div>
      <div class="panel-body">
        面板內容
      </div>
    </div>
    
  2. panel-danger 可改為其他class,以便換顏色,如 panel-info、 panel-primary、 panel-success 、 panel-warning...等

PHP7 入門研習

4-3 登入表單

一、Bootstrap3 表單

  1. 利用 <form> 語法來產生編輯表單
    <form class="form-horizontal" action="送至.php" method="post" role="form">
    表單元件
    </form>
    
  2. action是指定表單要送去哪個php檔做處理(接收變數)
  3. method 則選擇是要用post或get(預設)方式來傳遞變數
  4. BootStrap 的表單請參考:http://v3.bootcss.com/css/#forms
  5. 水平表單欄位的每一組 Bootstrap3 架構如下:
    <div class="form-group">
      <label class="col-md-3 control-label">標籤文字</label>
      <div class="col-md-9">
        表單元件(套用 class="form-control")
      </div>
    </div>
    
  6. 其中欄位寬度請視需求調整

二、登入表單設計

  1. 在表單中加入「表單欄位」:
    <form class="form-horizontal" action="index.php" method="post" role="form">
      <div class="form-group">
        <label class="col-md-4 control-label" for="name">帳號:</label>
        <div class="col-md-8">
          <input type="text" name="name" id="name" placeholder="請輸入帳號" class="form-control">
        </div>
      </div>
      <div class="form-group">
        <label class="col-md-4 control-label" for="passwd">密碼:</label>
        <div class="col-md-8">
          <input type="password" name="passwd" id="passwd" placeholder="請輸入密碼" class="form-control">
        </div>
      </div>
      <div class="text-center">
        <input type="hidden" name="op" value="login">
        <button type="submit" name="button" class="btn btn-primary">登入</button>
      </div>
    </form>
  2. 表單action指定到index.php,method使用post方法。
  3. 表單中一共有三個欄位:
    • name="name" 的文字(text)輸入框,送出後 index.php 會收到 $_POST['name'] 變數
    • name="passwd" 的密碼(password)輸入框 ,送出後 index.php 會收到 $_POST ['passwd '] 變數
    • name="op" 的隱藏(hidden)輸入框 ,送出後 index.php 會收到 $_POST ['op '] 變數
  4. op用來告訴程式下一個動作該做什麼(並不是規定的用法,只是一種技巧)。
  5. placeholder 是佔位字元,會有提示文字出現。
  6. 欄位套用 form-control 樣式,會顯得滿版圓潤。
  7. 欄位命名id,然後標籤用for來指定對應id,這只是為了符合無障礙2.0,不做對操作或版面均無影響。

PHP7 入門研習

4-4 讓sublime自動排版

  1. 在 sublime Text 中點選「Preferences→Package Control→Install  Package」,選擇要安裝的套件即可。
  2. 請輸入「HTML-CSS-JS Prettify」並安裝之。
  3. 下載 node.js 並安裝 https://nodejs.org/en/
  4. 在 sublime Text 中點選「Preferences→Package Settings→HTML-CSS-JS Prettify→ Set Prettify Preferences 」,在第10行加入 .tpl 副檔名
    "allowed_file_extensions": ["htm", "html", "xhtml", "shtml", "xml", "svg", "tpl"],
  5. 自動排版請按  Ctrl + Shift + H

PHP7 入門研習

4-5 引入樣板檔

  1. 把樣板做適當切割,可以讓程式碼清爽,甚至方便再利用。
  2. 可將剛剛的登入面板獨立成一個檔案:templates/side_login.tpl

一、側邊欄中引入登入畫面

  1. 開啟templates/index.tpl,將 side_login.tpl 引入:
    <div class="col-md-3">
      {include file='side_login.tpl'}
    </div>
    

二、精簡基本頁面

  1. 由於 <head><head>中的東西,每個頁面幾乎都一樣,所以,我們也可以將裡面的部份獨立出來成 templates/head.tpl 檔案,這樣可以讓頁面看起來更精簡:
    <head>
      <title>{$page_title}</title>
      <meta charset="utf-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <!--BootStrap-->
      <link rel="stylesheet" href="class/bootstrap/css/bootstrap.min.css">
    
      <!--jQuery-->
      <script src="class/jquery.min.js"></script>
      <script src="class/jquery-migrate.min.js"></script>
    
      <!--jQuery UI-->
      <link rel="stylesheet" href="class/jquery-ui/jquery-ui.min.css">
      <script src="class/jquery-ui/jquery-ui.min.js"></script>
    
      <!--Font awesome-->
      <link rel="stylesheet" href="class/font-awesome/css/font-awesome.min.css">
    </head>
  2. 其中<title>中改成樣板變數,以便讓每個頁面套用。
  3. 基本頁面部份就可以改成這樣:
    <!DOCTYPE html>
    <html lang="zh-Hant">
    {include file='head.tpl'}
    <body>
      <div class="container">
        <h1 class="page-header">{$page_title}</h1>
        <div class="row">
          <div class="col-md-9">
            <h2>主內容</h2>
          </div>
          <div class="col-md-3">
            <h2>Hi {$name}!</h2>
            {include file='side_login.tpl'}
          </div>
        </div>
      </div>
      <!--BootStrap js-->
      <script src="class/bootstrap/js/bootstrap.min.js"></script>
    </body>
    
    </html>
    
  4. index.php 則多送一個 page_title 的值到樣板
    <?php
    require_once 'smarty/libs/Smarty.class.php';
    $smarty = new Smarty;
    $name   = 'Tad';
    $smarty->assign('name', $name);
    $smarty->assign('page_title', '活動報名系統');
    $smarty->display('index.tpl');
    

     

PHP7 入門研習

5. 判斷是否登入

  1. 何謂登入?
    比對使用者輸入的帳號、密碼和系統中設定的帳號、密碼是否正確後,讓系統記住這個人已經登入。
  2. 使用者輸入的帳號、密碼→超級全域變數
  3. 系統中設定的帳號、密碼→可暫時用變數或常數來設定。
  4. 是否正確→if 判斷式
  5. 讓系統記住→session

PHP7 入門研習

5-1 接收變數

一、接收從表單來的變數

  1. 先看表單用什麼方法來傳變數,如果是post,那麼用 $_POST 來接收。
  2. 例如:$_POST['name'] 就是使用者輸入的帳號。中括號是陣列的寫法,裡面是索引(如:name)。
  3. $_POST 是「超級全域變數」

二、超級全域變數(superglobals)

  1. PHP提供了一系列的預設變數,這些預設變數都以陣列(Array)的型態存在。
  2. 不同來源的超級全域變數為 $_POST、$_GET、 $_REQUEST、$_SERVER、$_SESSION、$_COOKIE等,後面會陸續提到。
  3. 前面一定有底線,一定是大寫。
  4. 這些變數是可以在任何地方被拿來使用,例如函數中也可以直接用,不需要用global關鍵字宣告。
  5. 它們也被稱為「自動全域變數」(autoglobals)或 「超級全域變數」(superglobals)。

三、練習

  1. 試試接收來自登入表單的帳號,並將主畫面改成「Hi 接收的帳號」。
     

 

PHP7 入門研習

5-2 設定管理密碼

一、設定管理員帳密變數

  1. 開個新檔案 config.php
  2. 設定兩組變數,分別代表管理員帳密:
    $admin_id   = 'tad@tn.edu.tw';
    $admin_pass = '12345';
  3. PHP變數一律以$開頭。
  4. 其中「=」是指定運算元,將右邊的值,指派給左邊的變數
  5. 字串一律加上引號,數字可以不用。

PHP7 入門研習

5-3 過濾變數

零、體會一下被駭的感覺...

  1. 請在帳號輸入框分別輸入底下內容試試(XSS攻擊):
    • <img src="http://cdn.pingwest.com/wp-content/uploads/2015/07/hacker-big.jpg">
    • <script>alert('XSS')</script>

一、外來變數

  1. 用post方法傳過來的,我們用 $_POST['變數名稱'] 接收。
  2. 用get方法傳過來的,我們用 $_GET['變數名稱'] 接收。
  3. 用 $_REQUEST['變數名稱'] 同時可接收來自 post、get 和 cookie 的變數 。

二、過濾外來變數

  1. 外來變數通常來自使用者輸入或者比較容易竄改,所以,一定要進行過濾。
  2. 若用內建的 htmlspecialchars($string) 來過濾的話,預設只轉化雙引號,不對單引號做轉義,所以,這樣用htmlspecialchars($string,ENT_QUOTES) 更好。
  3. 另一個和 htmlspecialchars 很像的 htmlentities 函數並不適用中文,因為會連同中文字一起轉義。
  4. htmlentities htmlspecialchars 只能防止XSS攻擊,不能防止SQL隱碼攻擊。
    $title = htmlspecialchars($_POST['title'], ENT_QUOTES);

     

  5. 改用PHP的 filter_var 過濾器亦有同樣效果:
    $title = filter_var($_POST['title'], FILTER_SANITIZE_SPECIAL_CHARS);

     

三、 PHP的 filter_var 過濾器

  1. 可利用PHP內建的 filter_var() 函數來過濾變數。
  2. 幾種常用過濾方法,完整過濾器可由此查看
    名稱 功用
    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驗證

四、過濾數字

  1. 一般過濾數字用 (int) $xxxintval($xxx)即可,會強制把輸入的內容變成數字。
  2. 亦可用 filter_var($num, FILTER_SANITIZE_NUMBER_INT) 來過濾,但和 intval 不太一樣,例如:
    $num = "123其他文字456";
    echo filter_var($num, FILTER_SANITIZE_NUMBER_INT) . "<br>";
    echo (int) $num . "<br>";
    echo intval($num) . "<br>";
    

     

  3. 用用 (int) $xxxintval($xxx)會得到 123,但用filter_var() 會得到 123456

 

PHP7 入門研習

5-4 if 判斷式

一、PHP中 if 的用法

  1. if 用來讓程式可以做判斷:
    if(判斷條件){
      //條件為真執行
    }elseif(判斷條件){
      //elseif的條件為真執行
    }else{
      //條件為假時執行
    }
  2. 由上往下判斷,一旦為真,就執行指定動作。後面就不再繼續判斷。
  3. elseif 和 else 均可省略,視情況使用即可。
  4. elseif 可以有很多組,其餘的只能有一組。若是太多elseif,建議改用switch

二、Smarty 中 if 的用法

  1. 其使用方式和PHP差不多,只是長相不太一樣而已:
    {if 判斷條件}
      //條件為真執行
    {elseif 判斷條件}
      //elseif 的條件為真執行
    {else}
      //條件為假時執行
    {/if}

三、練習:

  1. 偵測是否有輸入,沒輸入時,請出現「Hi 訪客」。
     

PHP7 入門研習

5-5 用 $op 及 switch 迴圈來做流程控制

一、判斷有無 $op 變數並過濾之

  1. $op 是一個我們自己定義的變數,主要用來告知程式目前應該做什麼事情,所以一般會搭配 switch 流程控制來使用。
  2. $op 有可能用 post 或 get 方式傳遞,故一般我們習慣用 $_REQUEST['op'] 來接收。
  3. 先預設 $op 為空值,再用 isset() 判斷有無傳入 $_REQUEST['op'],若有才過濾之,並將過濾結果存回 $op。
    $op = '';
    if (isset($_REQUEST['op'])) {
      $op = filter_var($_REQUEST['op'], FILTER_SANITIZE_SPECIAL_CHARS);
    }
    
  4.  三元運算式精簡寫法(條件 ? 真動作一 :假動作二 ):
    $op = isset($_REQUEST['op']) ? filter_var($_REQUEST['op'], FILTER_SANITIZE_SPECIAL_CHARS) : "";

     

二、switch 用法

  1. switch 可以判斷某個變數值,當該變數值符合指定條件時,就去執行哪些動作,基本上就是「一個口令,一個動作」之意。
  2. switch 完整語法為:
    switch ($變數) {
      case '特定值':
        # 動作...
        break;
      
      default:
        # 動作...
        break;
    }
  3. case 到 break 就是完整一組,可以自行添加無限多組。
  4. break; 不加也符合語法,但會一直執行到下方動作。
  5. default 則是當變數跟任一個「特定值」都不相符時要進行的動作。

三、練習:

  1. 請利用 $op 來完成 switch 迴圈

PHP7 入門研習

5-6 檢查帳號密碼

一、引入設定檔

  1. include() 和 require() 都可以用來引入檔案,但原理不太一樣。
  2. require() :若引入不到檔案,會導致程式停擺。
  3. include():若引入不到檔案,僅秀出警告,程式仍繼續運作。
  4. require_once() 與 include_once() :引入進來的檔案若是相同的,只會引入一次。推薦使用!
  5. () 不一定要寫,寫成include "xxx.php"; 也可以。

二、判斷是否登入

  1. 為了讓系統記住登入狀態,我們使用了session,要使用session機制,必須先session_start();
    <?php
    /*引入檔案(初始設定)*/
    session_start();
    require_once 'config.php';
    require_once 'smarty/libs/Smarty.class.php';
    $smarty = new Smarty;
    
    /*過濾變數*/
    $op      = isset($_REQUEST['op']) ? filter_var($_REQUEST['op'], FILTER_SANITIZE_SPECIAL_CHARS) : "";
    $group   = isset($_SESSION['group']) ? $_SESSION['group'] : '';
    $name    = isset($_SESSION['name']) ? $_SESSION['name'] : '訪客';
    $content = "主內容";
    
    /*執行流程*/
    switch ($op) {
        case 'login':
            $name = isset($_POST['email']) ? filter_var($_POST['email'], FILTER_SANITIZE_SPECIAL_CHARS) : "訪客";
            $pass = isset($_POST['pass']) ? $_POST['pass'] : "";
            if ($name == $admin_id and $admin_pass == $passwd) {
                $_SESSION['group'] = 'admin';
                $_SESSION['name']  = $name;
                $content           = "登入成功";
            } else {
                $content = "登入失敗";
            }
            header("location:{$_SERVER['PHP_SELF']}");
            exit;
    
        default:
            # 取得活動列表
            break;
    }
    
    /*輸出結果*/
    $smarty->assign('name', $name);
    $smarty->assign('group', $group);
    $smarty->assign('content', $content);
    $smarty->assign('page_title', '活動報名系統');
    $smarty->display('index.tpl');
    
  2. 整理一下變數,把到時後要傳到樣板的放在上面即可,相當於預設值的感覺。
  3. 利用 switch 來判斷 $op 變數,隨著 $op 變數值不同,其對應動作也不一樣。登入時就跑login中的部份。
  4. 利用require_once()引入設定檔。
  5. 登入時會傳來 name 與 passwd,過濾帳號(因為最後要呈現到畫面上),密碼無須過濾。
  6. 過濾後,判斷和設定檔的帳密是否一致,若一致,就用$_SESSION['group']記住狀態。順便把名字也記住。
  7. 其中「==」是比較運算符,「=」則是賦值運算符。
  8. header() 也是PHP內建的函數,用來設定檔案檔頭。
  9. header("location: 網址") 的用法則是轉頁(轉向)的語法,但一樣會往下執行,所以,必須用 exit 告知程式到此為止,不再往下執行(因為要轉向了)。
  10. $content 理論上看不到,不過若是把轉向暫時註解,就可以看到登入成功或失敗。

PHP7 入門研習

5-7 登入後,畫面應有的變化

  1. 除了顯示姓名外,登入框應該不見,取而代之的可能是管理工具列。

一、製作工具列面板

  1. 建立 templates\side_tools.tpl
    <div class="panel panel-success">
      <div class="panel-heading">工具列</div>
    
      <!-- List group -->
      <div class="list-group">
        <a href="index.php" class="list-group-item">回首頁</a>
        <a href="admin.php" class="list-group-item">發布活動</a>
        <a href="index.php?op=logout" class="list-group-item">登出</a>
      </div>
    </div>
    
  2. 這是用 bootsrtap中的面板+列表組的應用。

二、讓系統自行切換

  1. 直接在樣板中判斷 $group 的值,以便做出不同對應
    <!DOCTYPE html>
    <html lang="zh-Hant">
    {include file='head.tpl'}
    <body>
      <div class="container">
        <h1 class="page-header">{$page_title}</h1>
        <div class="row">
          <div class="col-md-9">
            <h2>主內容</h2>
            {$content}
          </div>
          <div class="col-md-3">
            <h2>Hi {$name}!</h2>
            {if $group=='admin'}
              {include file='side_tools.tpl'}
            {else}
              {include file='side_login.tpl'}
            {/if}
          </div>
        </div>
      </div>
      <!--BootStrap js-->
      <script src="class/bootstrap/js/bootstrap.min.js"></script>
    </body>
    
    </html>
    

三、練習

  1. 請試著做出登出功能。

(溫馨提示:可以用unset() 移除掉 session 變數,也可以把 session 變數的值改掉。)

PHP7 入門研習

6. 把程式片段打包成函數

一、關於函數

  1. 函數有兩種,一組是PHP內建函數,另一種是自訂的函數。
  2. 一個函數通常都有其獨特的功能,可視為具特定功能的小零件,直接呼叫使用即可,如:phpinfo(),有些有傳回值,有些沒有;有些需要輸入參數,有些不用。
  3. 完整函數手冊:http://www.php.net/manual/en/funcref.php
  4. 函數基本結構:傳回值 函數名稱(參數1,參數2...);
  5. 函數傳回值有:string(字串)、int(整數)、array(陣列)、object(物件)、bool(布林值)、void(無傳回值)、mixed(不一定)、new(建立物件)

二、自訂函數

  1. 自訂函數的基本語法為:
    function 函數名稱($參數1='預設值' , $參數2='預設值',...){
    	global $外面的變數;
    	函數內容,任何有效的 PHP 程式碼,包括其它函數和class定義 ;
    	return 傳回值;
    }
  2. 參數不見得要有,傳回值也不一定要有。
  3. 例如把登入做成函數:
    function login()
    {
        require_once "config.php";
        $name   = isset($_POST['name']) ? filter_var($_POST['name'], FILTER_SANITIZE_SPECIAL_CHARS) : '';
        $passwd = isset($_POST['passwd']) ? $_POST['passwd'] : '';
        if ($name == $admin_id and $admin_pass == $passwd) {
            $_SESSION['group'] = 'admin';
            $_SESSION['name']  = $name;
        }
    }
  4. 超級全域變數可直接在函數中使用,外面的一般變數無法進到函數中,除非做成參數或是用 global 宣告。
  5. 當然,函數裡面的變數外面也無法取用,除非return出去。
  6. 函數放在檔案中任何地方都可以,放在呼叫之前或之後都沒關係。
  7. 要使用時,呼叫之即可
    switch ($op) {
        case 'login':
            login();
            header("location:{$_SERVER['PHP_SELF']}");
            exit;
    
        case "logout":
            header("location:{$_SERVER['PHP_SELF']}");
            exit;
    
        default:
            # code...
            break;
    }

     

 三、練習:

  1. 請把登出也做成函數

PHP7 入門研習

6-1 資料類型

  1. 不管變數常數,都會有值,只要有值,就會扯到值的資料類型。
  2. PHP有以下資料類型:
    1. 布林值(boolean):true、false,無須引號。
    2. 整數(integer):就數字123456,無須引號。
    3. 浮點數(float):有小數點的數字,如3.14,無須引號。
    4. 字串(string):一般文字,一定要有引號。
      • 雙引號中,變數有效,可用{}將變數和文字隔開,例如:echo "Hi {$name}!"; 就會印出「Hi 某某某!」
      • 單引號中,變數會失效, echo 'Hi {$name}!'; 就會秀出「Hi {$name}!」
    5. 陣列(array):可以放很多值的變數,形狀為:「$arra['索引']=值」
      1. 索引可以數字也可以文字,沒填索引預設會從0開始
      2. 一維陣列:
        $stud[1]="tad";
        $stud[2]="phebe";
        或
        $stud=[1=>"tad", 2=>"phebe"];
        或
        $stud=array(1=>"tad", 2=>"phebe");
        使用:
        echo $stud[1];
      3. 二維陣列:
        $stud[1][1]="tad";
        $stud[1][2]="phebe";
        $stud[2][1]="kiki";
        $stud[2][2]="huhui";
        或
        $stud = [
          1 => [1 => "tad", 2 => "phebe"],
          2 => [1 => "kiki", 2 => "huhui"]
        ];
        或
        $stud = array(
          1 => array(1 => "tad", 2 => "phebe"),
          2 => array(1 => "kiki", 2 => "huhui")
        );
        使用:
        echo $stud[2][1];
    6. 物件(object):可自行定義物件成員、物件方法等。
    7. 資源(resource):PHP在引用其他資源時所產生的一種類型。
    8. 無值(null):NULL不分大小寫,不須引號,代表沒東西或不存在。

PHP7 入門研習

6-2 PHP常用運算符(子)

  1. 算術運算符:也就是+(加)-(減)*(乘)/(除) %(餘數) 這類的運算符號。

     

    數學運算符 範例 範例解釋 範例結果
    +(加) $a + $b $a加$b 10+4的結果:14
    -(減) $a - $b $a減$b 10-4的結果:6
    *(乘) $a * $b $a乘以$b 10*4的結果:40
    /(除) $a / $b $a除以$b 10/4的結果:2.5
    %(求餘數) $a % $b $a除以$b的餘數 10%4的結果:2
  2. 賦值運算符:如$a=3,其中的=就是賦值運算符。
    $a = $a + 2;
    可改寫成
    $a += 2;
    
  3. 比較運算符:就是之前我們學的if(5>3),這類<、>、 <=、>=、==、!=的比較運算。
    例子 運算符意義 解釋
    $a == $b ==相等 $a和$b的值相等時,才為真
    $a === $b ===全等 $a和$b的值以及資料形態都相等時才為真!
    $a != $b !=不相等 $a和$b的值不相等時,才為真
    $a <> $b !=不相等 $a和$b的值不相等時,才為真
    $a !== $b !==不全等 $a和$b的值或資料形態不相等時才為真!
    $a < $b <小於 $a小於$b才為真
    $a > $b >大於 $a大於$b才為真
    $a <= $b <=小於等於 $a小於或等於$b才為真
    $a >= $b >=大於等於 $a大於或等於$b才為真
    $a <=> $b 比較 PHP7才新增的 <=> 運算符,只會傳回 -1、0、1三種值!
    -1,代表左邊小於右邊;
    1,代表左邊大於右邊;
    0,那就代表左右兩邊相等。
  4. 錯誤控制運算符:指的是@這個符號,可以抑制錯誤訊息產生。
  5. 遞增、遞減運算符:如++$a這類的用法。
    運算符 意義 說明
    ++$a 先遞增 $a值加1之後才傳回$a值
    $a++ 後遞增 先傳回$a值之後再將$a值加1
    --$a 先遞減 $a值減1之後才傳回$a值
    $a-- 後遞減 先傳回$a值之後再將$a值減1
  6. 邏輯運算符:也就是and、or這類的用法。
    範例 邏輯運算符 意義
    $a and $b and(與) 只有$a 與 $b兩者皆為真,結果才為真
    $a && $b and(與) 只有$a 與 $b兩者皆為真,結果才為真
    $a or $b or(或) 只要$a 或 $b兩者之一為真,結果就為真
    $a || $b or(或) 只要$a 或 $b兩者之一為真,結果就為真
    $a xor $b exclusive or(互斥) 只有$a 與 $b一為真、一為假時,結果才為真
    !$a not(否) 只有$a為假時,結果才為真
  7. 字串運算符:小黑點「.」,用來連接字串和變數、常數或函數用的!
    echo "嗨!" . $user_name . "您好!";
    echo "資料庫名稱:" . _DB_NAME;
    echo "今天是:" . date("Y年m月d日");

     

PHP7 入門研習

7. 資料庫規劃

實際上,這應該是寫系統最先要做的事,不過,由於大家都是剛入門,所以,擺到這裡才開始說。

一、規劃資料表結構

  1. 建議先用試算表軟體,如:Calc或Excel來規劃一下(內含完整架構及模擬資料),順便想像一下需要的功能和欄位。
  2. 大致確定後,再利用adminer或phpMyAdmin等資料庫管理軟體來建立程式所需要的資料表

二、利用adminer建立資料庫及資料表

  1. 啟動adminer或者直接連至 http://localhost/us_opt2/index.php
  2. 帳密預設為 root 及 12345
  3. 點擊「建立新資料庫」,校對選擇「utf8_general_ci」
  4. 點擊「建立資料表」,依序建立相關資料表,底下是活動(actions)資料表
  5. 送出後觀察一下有無建立出主索引,若沒有需確定流水號欄位有勾選「AI」,並手動建立主索引欄位。
  6. users的資料表:
  7. 若是主索引是由兩個欄位組合而成的,那麼請別設定AI(自動遞增),底下是signups資料表
  8. 並用「修改索引」手動新增主索引。
  9. 接著,設定要組合成主索引的欄位即可

三、匯出資料表

  1. 點擊左邊「匯出」,將檔案存成 sql檔。
  2. sql是一個純文字檔,日後可以用來匯入以便快速建立資料庫結構,其中的註解建議都刪除。

 

PHP7 入門研習

7-1 常用資料庫的欄位類型

謂何需要了解欄位類型?

因為選用正確的欄位類型可以:

一、數字

資料型態 資料範圍
tinyint(4) -128至127
unsigned:0至255
smallint(6) -32768至32767
unsigned:0至65535
mediumint(9) -8388608至8388607
unsigned:0至16777215
int(11) -2147683648至2147683647
unsigned:0至4294967295
bigint(20) -9223372036854775808至9223372036854775807
unsigned:0至18446744073709551615

二、浮點數

資料型態 範圍範例(以MySQL>3.23為例)
decimal[(65[,30])] decimal(4,1)-999.9到9999.9
decimal(5,1)-9999.9到99999.9
decimal(6,1)-99999.9到999999.9
decimal(6,2)-9999.99到 99999.99
decimal(6,3)-999.999到9999.999
float[(255,30)] float(4,1)-999.9到999.9
float(5,1)-9999.9到9999.9
float(6,1)-99999.9到99999.9
float(6,2)-9999.99到 9999.99
float(6,3)-999.999到999.999
double[(255,30)] double(4,1)-999.9到999.9
double(5,1)-9999.9到9999.9
double(6,1)-99999.9到99999.9
double(6,2)-9999.99到 9999.99
double(6,3)-999.999到999.999

三、日期時間

資料型態 範圍
date 1000-01-01至9999-12-31
西元年可用4或2個數字,使用2個數字時,70到99表示1970到1999;
如果是00到69就是2000到2069,有點容易搞錯,所以年份最好還是寫完整4位數比較沒困擾。
datetime 1000-01-01 00:00:00至9999-12-31 23:59:59
timestamp 1970-01-01 00:00:01 UTC到2038-01-19 03:14:07 UTC
其格式與datetime一樣,但儲存空間只需要一半。
time -838:59:59至 838:59:59
存入「150:30:00」而言就是過了150小時又30分鐘之意。
亦可存入「-6:20:00」,意指「6個小時又20分鐘前」
year[(4|2)] 4位數字可以儲存的範圍從1901到2155;
2位數字的範圍從00到99,實際的西元年份是1970到2069,也就是說,當您存入00時,實際代表2000之意;
存入69代表2069;存入70代表1970;存入99代表1999。

四、字串

資料型態 最大長度 實際儲存的空間
char[(255)] 255 指定的長度
varchar(65535) 65535 指定的長度加1或2bytes
tinytext 255 指定的長度加1byte
text 65535 指定的長度加2bytes
mediumtext 16772215 指定的長度加3bytes
longtext 4294967295 指定的長度加4bytes

五、列表

資料型態 最大個數 儲存空間
enum(字串值[,...]) 65535 1byte(25個以內);
2bytes(256到65535個)
set(字串值[,...]) 64 1byte(8個以內);
2bytes(16個以內);
3bytes(24個以內);
4bytes(32個以內);
8bytes(64個以內)

六、二進位  

資料型態 最大長度 實際儲存的空間
bit[(8|64)] 64 bit的範圍為0到1
bit(8)的範圍為0到255
bit(64)的範圍為0到18446744073709551615
binary[(255)] 255 指定的長度
varbinary(65535) 65535 指定的長度加1或2bytes
tinyblob 255 指定的長度加1byte
blob 65535 指定的長度加2bytes
mediumblob 16772215 指定的長度加3bytes
longblob 4294967295 指定的長度加4bytes

PHP7 入門研習

8. 增加註冊機制

一、註冊表單

  1. 為了日後管理方便,我們把會員改成資料庫版
  2. 為了簡化登入機制,帳號以Email來取代
  3. 註冊表單可以拿登入表單來修改,並另存為 templates\regist_form.tpl,例如:
    <div class="panel panel-success">
      <div class="panel-heading">註冊</div>
      <div class="panel-body">
        <form class="form-horizontal" action="index.php" method="post" role="form">
          <div class="form-group">
            <label class="col-md-4 control-label" for="email">Email:</label>
            <div class="col-md-8">
              <input type="text" name="email" id="email" placeholder="請輸入Email" class="form-control">
            </div>
          </div>
          <div class="form-group">
            <label class="col-md-4 control-label" for="pass">密碼:</label>
            <div class="col-md-8">
              <input type="text" name="pass" id="pass" placeholder="請設定密碼" class="form-control">
            </div>
          </div>
          <div class="form-group">
            <label class="col-md-4 control-label" for="name">姓名:</label>
            <div class="col-md-8">
              <input type="text" name="name" id="name" placeholder="請輸入中文姓名" class="form-control">
            </div>
          </div>
          <div class="text-center">
            <button type="submit" name="op" value="save_regist" class="btn btn-primary">確定註冊</button>
          </div>
        </form>
      </div>
    </div>
    
  4. 其中,密碼改為明碼,避免使用者輸入錯誤。或者也可以改成輸入兩次密碼,然後進行比對亦可(只是比較麻煩)。

  5. 另外,記得設定 op,以便告訴程式下一步要做什麼。

  6. 當然原本的登入表單 templates\side_login.tpl 也要改一下帳號部份,另外,還得加上註冊的連結:

    <div class="panel panel-primary">
      <div class="panel-heading">登入</div>
      <div class="panel-body">
        <form class="form-horizontal" action="index.php" method="post" role="form">
          <div class="form-group">
            <label class="col-md-4 control-label" for="email">Email:</label>
            <div class="col-md-8">
              <input type="text" name="email" id="email" placeholder="請輸入Email" class="form-control">
            </div>
          </div>
          <div class="form-group">
            <label class="col-md-4 control-label" for="pass">密碼:</label>
            <div class="col-md-8">
              <input type="password" name="pass" id="pass" placeholder="請輸入密碼" class="form-control">
            </div>
          </div>
          <div class="text-center">
            <input type="hidden" name="op" value="login">
            <a href="index.php?op=regist" class="btn btn-success">註冊</a>
            <button type="submit" name="button" class="btn btn-primary">登入</button>
          </div>
        </form>
      </div>
    </div>
    
  7. 由於有多設了一組 op,所以,在 switch 中要多一組對應流程(即便沒有任何內容也沒關係)
    case 'regist':
         break;

     

二、如何點擊註冊就換成註冊表單?

  1. 側邊欄的切換是在 templates\index.tpl 中設定,所以,我們只要讓 index.tpl 知道何時該出現註冊的樣板即可。
  2. 由於按下註冊按鈕會傳出一個 $op=regist 的變數,所以,我們利用它來讓系統判斷即可。
    {if $op=='regist'}
      {include file='regist_form.tpl'}
    {else}
      {$content}
    {/if}
  3. 問題是,index.tpl如何取得 $op 變數?當然得由index.php傳給他!所以,修改 index.php,多傳一個 $smarty->assign('op', $op); 到樣板。

    require_once 'smarty/libs/Smarty.class.php';
    $smarty = new Smarty;
    $smarty->assign('name', $name);
    $smarty->assign('group', $group);
    $smarty->assign('content', $content);
    $smarty->assign('op', $op); 
    $smarty->assign('page_title', '活動報名系統');
    $smarty->display('index.tpl');

     

PHP7 入門研習

8-1 連上資料庫

一、連線到MySQL資料庫

  1. 要儲存東西到資料庫,必須先連線資料庫,以便PHP對MySQL執行SQL語法。
  2. 注意!PHP7已經不支援MySQL系列函數,而是改用MySQLi,或者PDO物件。
  3. 使用PDO物件的好處是方便介接其他資料庫,而用MySQLi的優點則是其運作和早期MySQL較像,熟悉MySQL函數的話,幾乎可以無痛轉移。
  4. 底下還是以MySQLi為範例。
  5. 基本MySQL資料庫連線方法:
    function link_db()
    {
        $mysqli = new mysqli(_DB_LOCATON, _DB_ID, _DB_PASS, _DB_NAME);
        if ($mysqli->connect_error) {
            die('無法連上資料庫:' . $mysqli->connect_error);
        }
        $mysqli->set_charset("utf8");
        return $mysqli;
    }
    
  6. 我們將之做成函數,並且傳出$mysqli物件,以便繼續利用。
  7. 由於該函數可能到處都會用到,所以,另外建立一個function.php檔案(方便其他php引入),並將該函數放裡面,好讓其他檔案可以隨時取用該函數。
  8. 我們可以利用常數來作為參數。

二、常數

  1. 常數是一定設定就不會變,和變數可以隨時指派其值不一樣。
  2. 常數可以直接在函數中使用
  3. 一般常數會以大寫前面加底線來辨識(實際上,不加底線或是用小寫也是可以)。

三、將資料庫設定放入共同設定檔 config.php

  1. 由於我們有管理員帳密,這裡又有資料庫帳密設定,這些通常好幾個頁面都會用到,所以,可以獨立一個頁面來放這些設定,以方便統一管理。
  2. 我們設定一個 config.php,內容如下:
    <?php
    $admin_id   = 'tad';
    $admin_pass = '12345';
    
    //資料庫位址
    define('_DB_LOCATON', 'localhost');
    //資料庫帳號
    define('_DB_ID', 'root');
    //資料庫密碼
    define('_DB_PASS', '12345');
    //資料庫名稱
    define('_DB_NAME', 'signup');
  3. 管理員帳密用變數,資料庫用常數,這只是為了做範例,您也可以一律用常數,或一律用變數。

 

PHP7 入門研習

8-2 新增使用者到資料庫

一、加入註冊流程

  1. 由於註冊表單的 action 是指向到 index.php,也就是註冊會在 index.php中處理。
  2. 由於有送一個 $op='save_regist' 到 index.php,所以,在 index.php 的 switch 中加入一組對應。
    case "save_regist":
        save_regist();
        header("location:{$_SERVER['PHP_SELF']}");
        exit;
    
  3. 也就是註冊動作交由 save_regist() 函數來處理

  4. 註冊完,記得轉向,避免一直保留在「正在註冊的狀態」,若是沒轉向,只要一重新整理,資料庫便會多出一筆一樣的資料。

二、insert 語法

  1. 新增資料的SQL語法如下:
    insert [into] 資料表名稱 [(欄位1,欄位2...)] values (值1,值2...)
  2. 中括號的部份代表可以省略。

三、完成註冊函數

  1. 寫入前需先連上資料庫,故呼叫 link_db() 連上資料庫後,把連線控制器存到 $db 中以便後續使用。
  2. 由於需要config.php中的值,所以,記得把引入config.php的語法拉到網頁最上方,順便引入 function.php 共同函數檔。
    <?php
    session_start();
    require_once "config.php";
    require_once "function.php";
    require_once 'smarty/libs/Smarty.class.php';
    $smarty = new Smarty;
    $db = link_db();
  3. 為了讓 config.php 中的 $admin_id 變數以及 $db 資料庫物件可以在函數中使用,我們用 global 宣告該變數為全域變數。
  4. 利用 $db->real_escape_string() 過濾資料,目的是順利讓所有資料存入資料庫,並避免隱碼攻擊。
    //新增使用者
    function save_regist()
    {
        global $admin_id, $db;
        $name  = $db->real_escape_string($_POST['name']);
        $email = $db->real_escape_string($_POST['email']);
        $pass  = $db->real_escape_string($_POST['pass']);
        $group = $name == $admin_id ? 'admin' : 'user';
    
        $sql = "INSERT INTO `users` ( `name`, `email`, `pass`, `group`)
        VALUES ('{$name}', '{$email}', '{$pass}', '{$group}')";
        $db->query($sql) or die($db->error);
        $uid = $db->insert_id;
        return $uid;
    }
  5. $db->query($sql) 就是送執行指令到資料庫。
  6. $db->error 會秀出資料庫傳回來的錯誤訊息
  7. 其中 $group 群組判斷,我們根據 config.php 中的管理員帳號設定$admin_id來比對姓名,如果輸入的姓名也是 tad 的,就判為管理員admin,其餘為user(當然,這不是好的作法,改成用Email判斷會更好。)

四、練習

  1. 新增幾個使用者試試
  2. 利用adminer觀察有無新增成功

PHP7 入門研習

8-3 註冊哪有這麼簡單!

做到這邊很高興終於能新增使用者了,以為以後高枕無憂了?還早的呢...

  1. 萬一重複註冊怎麼辦?
  2. Email亂填怎麼辦?
  3. 部份欄位沒填怎麼辦?
  4. 密碼會被管理員看到!

所以,要注意的東西多著呢!我們一個一個來處理。

一、避免重複註冊

  1. 這個最簡單,從資料表結構下手即可。
  2. 只要將 email 設定成唯一欄位即可。
  3. 故意重複一下Email格式試試會有什麼結果吧!

二、檢查Email格式

  1. 若是想檢查 Email 是否有效,最好的方法當然是寄個啟動信到該信箱,讓使用者點擊後才正式啟用(等有空再來做)。
  2. 在這裡,我們先就「形狀」或者說「Email格式」來做檢查即可,利用之前學到的filter_var來處理即可。若格式正確,會傳回Email,若不正確,會傳回false。
    $email = $db->real_escape_string($_POST['email']);
    $email = filter_var($email, FILTER_VALIDATE_EMAIL);
    if (!$email) {
        die("不合法的Email");
    }

三、檢查必填欄位

  1. 其實上述的Email和此處的必填欄位都可以在前端做檢查,但實際上,前端的檢查是可以有技巧的跳過,因此,後端的檢查還是必要的。
  2. 用簡單的 empty() 函數就可以判斷是否為空值了。或者用 if($變數=="")也可以。
    $name = $db->real_escape_string($_POST['name']);
    if (empty($name)) {
        die("姓名為必填!");
    }
    $email = $db->real_escape_string($_POST['email']);
    if (empty($email)) {
        die("Eamil為必填!");
    }
    $email = filter_var($email, FILTER_VALIDATE_EMAIL);
    if (!$email) {
        die("不合法的Email");
    }
    $pass = $db->real_escape_string($_POST['pass']);
    if (empty($pass)) {
        die("密碼為必填!");
    }

四、密碼加密

  1. 目前最佳的密碼加密方法: password_hash()
  2. 不可逆!同一個密碼加密後的值會不一樣!
  3. 可利用 password_verify($password, $hash) 來解密!
    $pass = $db->real_escape_string($_POST['pass']);
    if (empty($pass)) {
        die("密碼為必填!");
    }
    $pass  = password_hash($pass, PASSWORD_DEFAULT);

五、練習

  1. 想想看,可以乾脆把 Email 設成主索引嗎?優缺點為何?
  2. 請匯出最新的資料庫結構檔並儲存。
  3. 清空(非刪除)資料表,並重新註冊正式會員

PHP7 入門研習

8-4 異常處理

  1. 遇到錯誤就直接die(),實際上並不太好。
    • 一來有些控制器可能還沒執行完或者沒有正常關閉。
    • 二來畫面實在很醜。
  2. 此時,我們可以利用PHP內建的異常處理機制。

一、拋出異常

  1. 把die()直接換成,如:throw new Exception()
    if (empty($name)) {
        throw new Exception("姓名為必填!");
    }
  2. 可用 Ctrl + H 來全部取代

  3. 有些在 or 後面的要改成:

    if (!$db->query($sql)) {
        throw new Exception($db->error);
    }

二、接收異常,並處理之

  1. 凡是有可能出現異常情形的,一般用try{} catch{}來執行並接收異常,例如:我們 try 整個 switch,只要其中有任何異常,都可以用 catch 去捕捉到!
    try
    {
        switch ($op) {
            case 'login':
                login();
                header("location:{$_SERVER['PHP_SELF']}");
                exit;
     
            case "logout":
                logout();
                header("location:{$_SERVER['PHP_SELF']}");
                exit;
     
            case "save_regist":
                save_regist();
                header("location:{$_SERVER['PHP_SELF']}");
                exit;
     
            default:
                # code...
                break;
        }
    } catch (exception $e) {
        $error = $e->getMessage();
    }
  2. 若想知道更多方法,可參考:http://php.net/manual/en/language.exceptions.extending.php

  3. 我們可以把 $error 送到樣板檔(檔案最前面記得給$error一個預設值):

    require_once 'smarty/libs/Smarty.class.php';
    $smarty = new Smarty;
    $smarty->assign('name', $name);
    $smarty->assign('group', $group);
    $smarty->assign('op', $op);
    $smarty->assign('error', $error);
    $smarty->assign('page_title', '活動報名系統');
    $smarty->display('index.tpl');
  4. 做一個新的樣板檔 \templates\alert_error.tpl,內容為:

    <div class="alert alert-danger">
      <h2>{$error}</h2>
    </div>
    
  5. 接著在 index.tpl 主樣板檔中判斷是否有 $error,若有,引入上面的樣板檔,以呈現錯誤訊息。

    <div class="col-md-9">
      {if $error}
        {include file='alert_error.tpl'}
      {/if}
    </div>

     

PHP7 入門研習

9. 從資料庫中讀取資料的方法

一、從資料庫中讀取資料

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

PHP7 入門研習

9-1 改寫 login()

一、改寫login()

  1. 原理說明:
    1. 先過濾並檢查變數(帳號)
    2. 拿帳號(email)到資料庫取抓取資料
    3. 若有,比對密碼是否正確,若正確,寫入session(包括姓名、群組、Email、編號)
    4. 若無,則登入失敗。
  2. 重新改寫login()變成資料庫版:
    //登入
    function login()
    {
        global $db;
        $email = $db->real_escape_string($_POST['email']);
        if (empty($email)) {
            throw new Exception("Eamil為必填!");
        }
        $email = filter_var($email, FILTER_VALIDATE_EMAIL);
        if (!$email) {
            throw new Exception("不合法的Email");
        }
    
        $pass = isset($_POST['pass']) ? $_POST['pass'] : '';
    
        $sql    = "SELECT * FROM `users` where `email`='{$email}'";
        $result = $db->query($sql);
        if (!$result) {
            throw new Exception($db->error);
        }
        $data = $result->fetch_assoc();
    
        if (password_verify($pass, $data['pass'])) {
            $_SESSION['group'] = $data['group'];
            $_SESSION['name']  = htmlspecialchars($data['name'], ENT_QUOTES);
            $_SESSION['uid']   = $data['uid'];
            $_SESSION['email'] = htmlspecialchars($data['email'], ENT_QUOTES);
        } else {
            throw new Exception("登入失敗!");
        }
    }
  3. 一樣先整理輸入的Email
  4. 利用email為唯一值的特性,讀取屬於該帳號的所有身家資料(且預計只會有一筆)。
  5. 利用 $result->fetch_assoc() 取出資料陣列,並存入$data中以便取用。其中,$data的陣列內容類似這樣:
    • $data['uid']=1;
    • $data['name']='tad';
    • $data['email']='tad@tn.edu.tw';
    • $data['pass']='xxxxx';
    • $data['group']='admin';
  6. 若是改用 $result->fetch_row() 取出資料陣列,$data的陣列內容則類似這樣(通常會搭配 list() 一起使用):
    • $data[0]=1;
    • $data[1]='tad';
    • $data[2]='tad@tn.edu.tw';
    • $data[3]='xxxxx';
    • $data[4]='admin';
  7. 利用 password_verify($pass, $data['pass']) 來檢查密碼是否正確
    1. 若正確,定義$_SESSION['group'] 及 $_SESSION['name'] 等值,表示登入。
    2. 若不正確,則丟出異常訊息。

二、修改登出

  1. 既然登入紀錄的東西已經不一樣,登出也要跟著改
    //登出
    function logout()
    {
        unset($_SESSION['group']);
        unset($_SESSION['name']);
        unset($_SESSION['uid']);
        unset($_SESSION['email']);
    }
    

三、練習:

  1. 請把要存入資料庫的值過濾、檢查部份寫成函數 clean_var(),並放到function.php中,以簡化程式。例如:
    $name = $db->real_escape_string($_POST['name']);
    if (empty($name)) {
        throw new Exception("姓名為必填!");
    }
    $email = $db->real_escape_string($_POST['email']);
    if (empty($email)) {
        throw new Exception("Eamil為必填!");
    }
    $email = filter_var($email, FILTER_VALIDATE_EMAIL);
    if (!$email) {
        throw new Exception("不合法的Email");
    }
    $pass = $db->real_escape_string($_POST['pass']);
    if (empty($pass)) {
        throw new Exception("密碼為必填!");
    }

    可簡化為

    $name  = clean_var('name', '姓名');
    $email = clean_var('email', 'Eamil', FILTER_VALIDATE_EMAIL);
    $pass  = clean_var('pass', '密碼');
  2. 參考答案:

    //檢查並傳回欲拿到資料使用的變數
    function clean_var($var = '', $title = '', $filter = '')
    {
        global $db;
        $clean_var = $db->real_escape_string($_REQUEST[$var]);
        if (empty($clean_var)) {
            throw new Exception("{$title}為必填!");
        }
        if ($filter) {
            $clean_var = filter_var($clean_var, $filter);
            if (!$clean_var) {
                throw new Exception("不合法的{$title}");
            }
        }
        return $clean_var;
    }
    

     

 

PHP7 入門研習

9-2 改寫工具列

一、修改 index.tpl 樣板檔

  1. 現在一般會員也可以登入了,所以,必須至少有個登出的工具列。
  2. 判斷是否引入工具列的檔案為 index.tpl 樣板檔,我們稍微修改一下即可,只要 $group 的值存在,就表示已經登入。
    {if $group}
      {include file='side_tools.tpl'}
    {else}
      {include file='side_login.tpl'}
    {/if}

 二、修改 side_tools.tpl 樣板檔

  1. 主要是根據身份顯示不同選項:
    <div class="panel panel-success">
      <div class="panel-heading">工具列</div>
    
      <!-- List group -->
      <div class="list-group">
        <a href="index.php" class="list-group-item">回首頁</a>
        {if $group=="admin"}
          <a href="admin.php" class="list-group-item">發布活動</a>
        {elseif $group=="user"}
          <a href="my.php" class="list-group-item">我參加的活動</a>
        {/if}
        <a href="index.php?op=logout" class="list-group-item">登出</a>
      </div>
    </div>
    

     

PHP7 入門研習

10. 製作活動管理頁面

一、產生 admin.php

  1. 複製 index.php 為 admin.php,其中函數以及 switch 部份 case 可以先移除。
  2. 於開頭加入是否為管理員的判斷。
    <?php
    session_start();
    require_once "config.php";
    require_once "function.php";
    require_once 'smarty/libs/Smarty.class.php';
    $smarty = new Smarty;
    $db = link_db();
    
    $op    = isset($_REQUEST['op']) ? htmlspecialchars($_REQUEST['op'], ENT_QUOTES) : '';
    $group = isset($_SESSION['group']) ? $_SESSION['group'] : '';
    $name  = isset($_SESSION['name']) ? $_SESSION['name'] : '訪客';
    $content = $error = '';
    if ($group != 'admin') {
        header("location: index.php");
        exit;
    }
    try
    {
        switch ($op) {
    
            default:
                # code...
                break;
        }
    } catch (exception $e) {
        $error = $e->getMessage();
    }
    
    $smarty->assign('name', $name);
    $smarty->assign('group', $group);
    $smarty->assign('content', $content);
    $smarty->assign('op', $op);
    $smarty->assign('error', $error);
    $smarty->assign('page_title', '活動管理');
    $smarty->display('index.tpl');
    
    
  3. 樣板一樣用同樣的 index.tpl 即可。

  4. 請確定在沒有登入時,是無法看到 http://localhost/signup/admin.php 頁面的。

二、練習

  1. 修改 index.tpl 使之適用各種檔案(至少標題要能變化)。

PHP7 入門研習

10-1 製作頁首、頁尾檔

一、為何需要頁首、頁尾檔?

  1. 因為檔案的前後常常是重複的
  2. 為了避免以後要修改一堆檔案

二、頁首檔 header.php

  1. 把 index.php 和 admin.php 上方相同的部份取出
  2. 存成 header.php
    <?php
    session_start();
    require_once "config.php";
    require_once "function.php";
    require_once 'smarty/libs/Smarty.class.php';
    $smarty = new Smarty;
    $db = link_db();
     
    $op    = isset($_REQUEST['op']) ? htmlspecialchars($_REQUEST['op'], ENT_QUOTES) : '';
    $group = isset($_SESSION['group']) ? $_SESSION['group'] : '';
    $name  = isset($_SESSION['name']) ? $_SESSION['name'] : '訪客';
    $content = $error = '';
  3. 最後用 require_once 引入該檔即可(index.php也要比照辦理)
    <?php
    require_once "header.php";
    
    if ($group != 'admin') {
        header("location: index.php");
        exit;
    }

三、頁尾檔 footer.php

  1. 把 index.php 和 admin.php 下方相同的部份取出
  2. 存成 footer.php
    <?php
    $smarty->assign('name', $name);
    $smarty->assign('group', $group);
    $smarty->assign('content', $content);
    $smarty->assign('op', $op);
    $smarty->assign('error', $error);
    $smarty->assign('page_title', $page_title);
    $smarty->display('index.tpl');
    
  3. 其中,為了讓 page_title 可以每頁不同,所以,將之設成變數。因此,我們可以在引入頁尾前,設定一下該變數
  4. 最後用 require_once 引入該檔即可(index.php也要比照辦理)
    try
    {
        switch ($op) {
    
            default:
                $content = action_form();
                break;
        }
    } catch (exception $e) {
        $error = $e->getMessage();
    }
    
    $page_title = '活動管理';
    require_once "footer.php";
    

     

PHP7 入門研習

10-2 用物件來做表單

一、為何要用物件來做表單?

  1. 優點
    • 不用自己手動刻表單,方便省事
    • 做修改功能時可省下大量功夫!
    • 未來可無縫轉換BootStrap4
    • 內含驗證及檢查功能
    • 內建編輯器等一些常用外掛
  2. 缺點
    • 版面缺乏彈性
    • 想要套用其他jquery套件會有點麻煩

二、關於PHP-Bootstrap-Form

  1. 官網:http://smarttechdo.com
  2. 安裝:下載 php-bootstrap-form-4.0.zip,並解壓縮至 class 下改名為 php-bootstrap-form

三、使用之產生表單

  1. 建立函數 action_form() 用來產生
  2. 由於內建的月曆實在難用,所以,可以自行套用其他的月曆程式
  3. 在裡面引入"class/php-bootstrap-form/PFBC/Form.php
    function action_form()
    {
        require_once "class/php-bootstrap-form/PFBC/Form.php";
        $values = [];
        ob_start();
        echo '<script type="text/javascript" src="class/My97DatePicker/WdatePicker.js"></script>';
        Form::open("action", $values);
        Form::Hidden("op", 'insert_action');
        Form::Textbox("活動名稱", "title", ['required' => 1]);
        Form::Textbox("活動日期", "action_date", ['required' => 1, 'onClick' => "WdatePicker()"]);
        Form::Textbox("截止日期", "end_date", ['required' => 1, 'onClick' => "WdatePicker({dateFmt:'yyyy-MM-dd HH:mm:00'})"]);
        Form::YesNo("使否啟用", "enable");
        Form::CKEditor("活動內容", "content");
        Form::Button('儲存', 'submit');
        Form::close(false);
        $form = ob_get_contents();
        ob_end_clean();
        return $form;
    }
  4. $values = [];是個個欄位預設值,未來在修改時會用到

  5. 若新增時,想要替某些欄位設定預設值,直接填入 $values 陣列即可(要按照順序),例如:

    $values = [
        'title'       => '活動',
        'action_date' => date("Y-m-d", strtotime("+14 day")),
        'end_date'    => date("Y-m-d H:i:00", strtotime("+10 day")),
        'enable'      => 1,
        'content'     => '活動內容~~~',
    ];
  6. 其中 date() 用來格式化日期,strtotime() 用來產生時間戳記。
  7. 由於該表單物件會直接輸出,故利用 ob_start(); 將其輸出移至緩衝區,再利用 ob_get_contents(); 把輸出的東西取出來並存入 $form,最後用 ob_end_clean(); 釋放掉。

  8. 表單一律從 Form::open() 開始,Form::close() 結束,中間便是表單欄位。

  9. 表單預設的 action 位置就是該檔案本身,method 預設為post

  10. 引入 WdatePicker.js 目的是為了使用 My97DatePicker 小月曆

  11. 詳細表單欄位用法可參考:http://smarttechdo.com/~avb/pfbc/api/

  12. 亦可至 http://smarttechdo.com/~avb/pfbc/example.php 觀看範例。

四、小月曆的常用參數

  1. 其常用的參數如下,更多參數請看:http://www.my97.net/dp/demo/index.htm

PHP7 入門研習

10-3 寫入活動到資料庫

  1. 由於有隱藏 op=insert_action,所以,在 admin.php 中的 switch 中多一組對應設定:
    case "insert_action":
        $action_id = insert_action();
        header("location:index.php?action_id=$action_id");
        exit;
  2. 設定要去執行 insert_action() 函數,也就是用來新增活動到資料庫,並希望他能傳回活動編號,以便儲存後,可以直接連到首頁index.php去觀看該活動的詳細內容。

  3. 接著製作 insert_action() 函數,由於寫入的過程都差不多,在此會建議直接複製之前的寫入函數來修改會更快:

    //新增活動
    function insert_action()
    {
    
        global $db;
        $title       = clean_var('title', '活動名稱');
        $action_date = clean_var('action_date', '活動日期');
        $end_date    = clean_var('end_date', '截止日期');
        $enable      = clean_var('enable', '使否啟用');
        $content     = clean_var('content', '活動內容');
        $uid         = $_SESSION['uid'];
    
        $sql = "INSERT INTO `actions` ( `title`, `content`, `action_date`, `end_date`, `uid`, `enable`)
        VALUES ('{$title}', '{$content}', '{$action_date}', '{$end_date}', '{$uid}', '{$enable}')";
        if (!$db->query($sql)) {
            throw new Exception($db->error);
        }
        $action_id = $db->insert_id;
        return $action_id;
    }

     

PHP7 入門研習

11. 重新調整流程

一、調整流程

  1. 首先,先更新一下 index.php 的流程,在 default 的地方,我們根據 $action_id 來判斷是要讀出指定的活動,還是取得所有活動列表。
    $action_id = isset($_REQUEST['action_id']) ? intval($_REQUEST['action_id']) : '';
    switch ($op) {
        case 'regist':
            break;
    
        case 'login':
            login();
            header("location:{$_SERVER['PHP_SELF']}");
            exit;
    
        case "logout":
            logout();
            header("location:{$_SERVER['PHP_SELF']}");
            exit;
    
        case "save_regist":
            save_regist();
            header("location:{$_SERVER['PHP_SELF']}");
            exit;
    
        default:
            if ($action_id) {
                $op      = 'show_action';
                show_action($action_id);
            } else {
                $op      = 'list_action';
                list_action();
            }
            break;
    }
  2. 由於流程中會用到 $action_id,我們確定 $action_id 是個編號,所以,利用 intval() 去過濾之,並且接收來自任何方法的 $action_id,故用 $_REQUEST['action_id']。

  3. 由於頁尾檔有送 $op 直到樣板,因此,目前在進行的任何一個動作最好都有一個 op 值,故,我們在 default 中,針對那兩個函數各給一個 $op 值,以便樣板可以辨識目前要出現的內容為何。

  4. show_action($action_id) 及 list_action() 為什麼沒有傳回 $content?因為我們打算直接把內容,用樣板變數分別直接傳給樣板,所以,在此就不同一做出結果再傳回,而是直接送給樣板來處理,這樣會版面設計會更彈性。

  5. 此外,原本還有一個 op 是 regist,用來註冊用,在此,也要多一組設定。因為若沒有設定,switch會自動跑去 default,由於沒有編號,所以 op 會變成 list_action,如此,使用者將永遠看不到註冊表單。

 

PHP7 入門研習

11-1 列出所有活動

一、讀出所有資料

  1. 在 index.php 新增函數 list_action()
    //列出所有活動
    function list_action()
    {
        global $db, $smarty;
    
        $sql    = "SELECT * FROM `actions` order by action_date desc";
        $result = $db->query($sql);
        if (!$result) {
            throw new Exception($db->error);
        }
        $actions = [];
        while ($values = $result->fetch_assoc()) {
            $actions[] = $values;
        }
        $smarty->assign('actions', $actions);
    }
  2. 由於打算直接在函數中把呈現列表時,樣板需要的變數直接傳到樣板,因此,直接用 global 宣告 $smarty 物件,讓他可以在函數中使用。

  3. 撈資料時,我們以活動日期為排序依據,從新排到舊(日期大到小)

  4. 此時的 $actions 是一個二維陣列,第一個索引為第N筆資料,第二個索引則為某一筆資料的各個欄位。

  5. 從現實考量,這裡暫時不限制enable=1以及報名截止日的問題。

二、在樣板中呈現列表

  1. 我們可以讓樣板,根據 $op 值來引入不同樣板,列如:
    {if $op=="regist"}
      {include file='regist_form.tpl'} 
    {elseif $op=="list_action"}
      {include file='list_action.tpl'}
    {else}
      {$content}
    {/if}
  2. 也就是當執行列出活動時,就引入 list_action.tpl 作為主內容,而 list_action.tpl 只專心做列出活動的列表即可。

  3. list_action.tpl 的內容為:

    <h2>活動列表</h2>
    <table class="table table-hover table-striped">
      <thead>
        <tr class="info">
          <th>活動日期</th>
          <th>活動名稱</th>
          <th>報名截止日</th>
          <th>功能</th>
        </tr>
      </thead>
      <tbody>
      {foreach $actions as $action}
          <tr>
            <td>{$action.action_date}</td>
            <td><a href="index.php?action_id={$action.action_id}">{$action.title}</a></td>
            <td>{$action.end_date}</td>
            <td></td>
          </tr>
      {/foreach}
      </tbody>
    </table>
    

三、練習

  1. 「從現實考量,這裡暫時不限制enable=1以及報名截止日的問題。」請思考一下,是怎樣的考量?這樣的作法是否適當?會不會有什麼問題?有無更好作法?

 

PHP7 入門研習

11-2 列出單一活動

一、製作函數

  1. 讀出單一活動的流程已經有了,所以,直接完成函數show_action($action_id)
    //列出指定活動
    function show_action($action_id)
    {
        global $db, $smarty;
    
        $sql    = "SELECT * FROM `actions` where `action_id` ='{$action_id}'";
        $result = $db->query($sql);
        if (!$result) {
            throw new Exception($db->error);
        }
        $action = $result->fetch_assoc();
        $smarty->assign('action', $action);
    }

二、修改主樣板

  1. 編輯 templates\index.tpl
    {if $op=="regist"}
      {include file='regist_form.tpl'} 
    {elseif $op=="list_action"}
      {include file='list_action.tpl'}
    {elseif $op=='show_action'}
      {include file='show_action.tpl'}
    {else}
      {$content}
    {/if}

三、新增專屬樣板

  1. 新增 templates\show_action.tpl
    <h2>{$action.title}</h2>
    
    <div class="panel panel-default">
      <div class="panel-heading">活動日期:{$action.action_date}</div>
      <div class="panel-body">
        {$action.content}
      </div>
      <div class="panel-footer">報名截止日:{$action.end_date}</div>
    </div>
    

     

PHP7 入門研習

12. 加入管理功能

一、加入管理按鈕

  1. 修改  templates\list_action.tpl
    {foreach $actions as action}
        <tr>
          <td>{$action.action_date}</td>
          <td><a href="index.php?action_id={$action.action_id}">{$action.title}</a></td>
          <td>{$action.end_date}</td>
          <td>
            {if $group=="admin"}
              <a href="admin.php?action_id={$action.action_id}" class="btn btn-warning btn-xs">修改</a>
            {/if}
          </td>
        </tr>
    {/foreach}
  2. 在單一活動頁面也記得加,修改  templates\show_action.tpl,直接加在最下方即可
    {if $group=="admin"}
      <div class="text-center">
        <a href="admin.php?action_id={$action.action_id}" class="btn btn-warning">修改</a>
      </div>
    {/if}

二、修改流程

  1. 修改 admin.php 的流程,替原本的表單加入 $action_id
    $action_id = isset($_REQUEST['action_id']) ? intval($_REQUEST['action_id']) : '';
    switch ($op) {
    
        case "insert_action":
            $action_id = insert_action();
            header("location:index.php?action_id=$action_id");
            exit;
    
        default:
            $content = action_form($action_id);
            break;
    }

三、修改函數

  1. 函數 action_form() 原本就已經存在,我們只要加入一個新的參數 $action_id,並告訴函數,如果有參數 $action_id,就跟根據 $action_id 的值抓出該活動資料陣列。
    function action_form($action_id = '')
    {
        global $db;
        $values = [];
        $op     = 'insert_action';
        if ($action_id) {
            $sql    = "SELECT * FROM `actions` where `action_id`='{$action_id}'";
            $result = $db->query($sql);
            if (!$result) {
                throw new Exception($db->error);
            }
            $values = $result->fetch_assoc();
            $op     = 'update_action';
        }
        require_once "class/php-bootstrap-form/PFBC/Form.php";
        ob_start();
        echo '<script type="text/javascript" src="class/My97DatePicker/WdatePicker.js"></script>';
        Form::open("action", $values);
        Form::Hidden("op", $op);
        Form::Hidden("action_id", $action_id);
        Form::Textbox("活動名稱", "title", ['required' => 1]);
        Form::Textbox("活動日期", "action_date", ['required' => 1, 'onClick' => "WdatePicker()"]);
        Form::Textbox("截止日期", "end_date", ['required' => 1, 'onClick' => "WdatePicker({dateFmt:'yyyy-MM-dd HH:mm:00'})"]);
        Form::YesNo("使否啟用", "enable");
        Form::CKEditor("活動內容", "content");
        Form::Button('儲存', 'submit');
        Form::close(false);
        $form = ob_get_contents();
        ob_end_clean();
        return $form;
    }
  2. 其中 $op 的值也要根據狀況來給不同值,若是有 $action_id 的值,那就表示要修改,故設為 update_action

  3. 若是沒有,就表示是要新增,故 $op 的值設為 insert_action

  4. 另外,要修改時多一個隱藏欄位,將 action_id 的值送出,如此,修改時,才知道要修改哪一筆活動資料。

PHP7 入門研習

12-1 執行修改

一、修改流程

  1. 由於表單有多一個op隱藏欄位,故必須修改 admin.php 多加一組對應流程,用來呼叫 update_action() 以執行更新動作。
    case "update_action":
        update_action($action_id);
        header("location:index.php?action_id=$action_id");
        exit;
    

二、更新資料的SQL語法

  1. 更新資料的SQL語法:
    update `資料表名稱` set `欄位1`='值1', `欄位2`='值2', ... [where 篩選條件] [limit 筆數]
  2. 在 admin.php 中心新增函數:
    //更新活動
    function update_action($action_id)
    {
        global $db;
        $title       = clean_var('title', '活動名稱');
        $action_date = clean_var('action_date', '活動日期');
        $end_date    = clean_var('end_date', '截止日期');
        $enable      = clean_var('enable', '使否啟用');
        $content     = clean_var('content', '活動內容');
        $uid         = $_SESSION['uid'];
    
        $sql = "UPDATE `actions` SET
        `title`='{$title}',
        `content`='{$content}',
        `action_date`='{$action_date}',
        `end_date`='{$end_date}',
        `enable`='{$enable}',
        `uid`='{$uid}'
        WHERE `action_id`='{$action_id}'";
        if (!$db->query($sql)) {
            throw new Exception($db->error);
        }
    }
    

PHP7 入門研習

12-2 加入刪除功能

一、加入管理按鈕

  1. 修改  templates\list_action.tpl,在修改隔壁加入刪除功能
    {if $group=="admin"}
      <a href="admin.php?op=delet_action&action_id={$action.action_id}" class="btn btn-danger btn-xs">刪除</a>
      <a href="admin.php?action_id={$action.action_id}" class="btn btn-warning btn-xs">修改</a>
    {/if}
  2. 在單一活動頁面也記得加,修改  templates\show_action.tpl,一樣加在修改的隔壁
    {if $group=="admin"}
      <div class="text-center">
        <a href="admin.php?op=delet_action&action_id={$action.action_id}" class="btn btn-danger ">刪除</a>
        <a href="admin.php?action_id={$action.action_id}" class="btn btn-warning">修改</a>
      </div>
    {/if}

二、修改流程

  1. 修改 admin.php 的流程,加入一組刪除的流程
    case "delete_action":
        delete_action($action_id);
        header("location:index.php");
        exit;

三、刪除的SQL語法

  1. 刪除的SQL語法如下:
    delete from `資料表名稱` [where 篩選條件] [limit 筆數]

四、新增刪除函數

  1. 刪除應該是最簡單的函數了!
    //刪除活動
    function delete_action($action_id)
    {
        global $db;
    
        $sql = "DELETE FROM `actions` WHERE `action_id`='{$action_id}'";
        if (!$db->query($sql)) {
            throw new Exception($db->error);
        }
    }

五、練習

  1. 想想看,如果要做到:只能刪除自己開的活動該如何做?

PHP7 入門研習

12-3 確認後刪除

  1. 直接刪除太可怕了,完全無後悔的餘地!
  2. 所以,我們可以有兩種作法,一種是確定活動是停用或過期的,才能刪
  3. 另一種是確認後才能刪除!
  4. 當然,要兩種都做最好。

一、確認後刪除

  1. sweet alert官網:http://t4t5.github.io/sweetalert/
  2. 下載:https://github.com/t4t5/sweetalert/archive/master.zip
  3. 下載後,將 dist 複製到 class 下,並改為 sweet-alert
  4. 修改刪除按鈕為
    <a href="javascript:delete_action({$action.action_id})" class="btn btn-danger btn-xs">刪除</a>
  5. 將以下語法貼到有上述連結的樣板檔案中,或者存成 templates\sweet-alert.tpl:
    <script type="text/javascript" src="class/sweet-alert/sweetalert.min.js"></script>
    <link rel="stylesheet" type="text/css" href="class/sweet-alert/sweetalert.css" />
    <script type="text/javascript">
      function delete_action(id){
        swal({
          title: "確定要刪除嗎?",
          text: "刪除後資料就消失救不回來囉!",
          type: "warning",
          showCancelButton: true,
          confirmButtonColor: "#DD6B55",
          confirmButtonText: "是!含淚刪除!",
          cancelButtonText: "不...別刪",
          closeOnConfirm: false
        }, function(){
          swal("OK!刪掉惹!", "該資料已經隨風而逝了...", "success");
    
          location.href='admin.php?op=delete_action&action_id=' + id;
        });
      }
    </script>
  6. 然後分別在 show_action.tpl 和 list_action.tpl 中,引入該樣板檔即可:

    {include file='sweet-alert.tpl'}

     

PHP7 入門研習

13. 加入報名功能

剛剛都是以管理員角度看事情,現在請註冊幾個使用者,要開始以一般使用者的角度看系統了。

一、加入報名按鈕

  1. 編輯 list_action.tpl,修改列表的按鈕
    {if $group=="admin"}
      <a href="javascript:delete_action({$action.action_id})" class="btn btn-danger btn-xs">刪除</a>
      <a href="admin.php?action_id={$action.action_id}" class="btn btn-warning btn-xs">修改</a>
    {elseif $group=="user"}
      <a href="index.php?op=signup&action_id={$action.action_id}" class="btn btn-primary btn-xs">我要報名</a>
    {/if}
    <a href="index.php?action_id={$action.action_id}" class="btn btn-success btn-xs">詳情</a>
  2. 當然,活動頁面 show_action.tpl 的下方也是要加的!

    <div class="text-center">
      {if $group=="admin"}
        <a href="javascript:delete_action({$action.action_id})" class="btn btn-danger ">刪除</a>
        <a href="admin.php?action_id={$action.action_id}" class="btn btn-warning">修改</a>
      {elseif $group=="user"}
        <a href="index.php?op=signup&action_id={$action.action_id}" class="btn btn-primary btn-lg">我要報名</a>
      {/if}
    </div>

     

PHP7 入門研習

13-1 怎樣才叫做已經報名?

一、先回顧一下報名的資料表

  1. 咱的資料表很簡單
    CREATE TABLE `signups` (
      `uid` smallint(5) unsigned NOT NULL COMMENT '使用者編號',
      `action_id` smallint(5) unsigned NOT NULL COMMENT '活動編號',
      `signup_date` datetime NOT NULL COMMENT '報名日期',
      PRIMARY KEY (`uid`,`action_id`)
    ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
  2. 基本上只有三個欄位!

  3. 所謂報名,就是在這個表中多一筆紀錄,看是誰(uid),在什麼時候(signup_date),報了哪個活動(action_id)

  4. 基本上,此表沒有所謂審核的功能,有需要請自己加(研習哪來那麼多時間...)

二、加入報名函數

  1. 由於按鈕是連結到 index.php ,其 op 為 signup,所以,按照老方法,建流程,做函數。
  2. 修改流程,加入一組:
    case "signup":
        signup($action_id);
        header("location:{$_SERVER['PHP_SELF']}?action_id=$action_id");
        exit;
  3. 只要傳入編號即可,uid可以從 session抓,日期更是可直接由資料庫產生。

  4. 報名後,直接轉向該活動,我們希望可以在活動下方看到已經報名名單

三、製作報名函數

  1. 在 index.php 中新增 signup() 函數,新增報名資訊到 signups 中
    //報名
    function signup($action_id)
    {
        global $db;
    
        $uid = $_SESSION['uid'];
    
        $sql = "INSERT INTO `signups` ( `action_id`, `uid`, `signup_date`)
        VALUES ('{$action_id}', '{$uid}', NOW())";
        if (!$db->query($sql)) {
            throw new Exception($db->error);
        }
    }

     

PHP7 入門研習

13-2 列出已報名名單

  1. 要先思考,要列在哪裡?(右邊空空...要不放在右側?)
  2. 好,放右側...所以,我們得做一個右側的樣板...就暫名為side_signups.tpl 好了。
  3. 那...請問何時要顯示已報名列表?(點進去活動時?)
  4. 好,那什麼情況才叫做「點進去活動時」?(網頁連到index.php,且 op=show_action 時!)
  5. 沒錯!所以,我們可以做個函數,放到 index.php,然後在op=show_action 時呼叫該函數,讓該函數把報名列表的值丟到樣板去列出。

一、修改流程

  1. 開啟 index.php 修改其流程,加入 list_signup($action_id);
    default:
        if ($action_id) {
            $op = 'show_action';
            show_action($action_id);
            list_signup($action_id);
        } else {
            $op = 'list_action';
            list_action();
        }
        break;

二、製作函數

  1. 一樣在 index.php 中,新增 list_signup() 函數
    //已報名名單
    function list_signup($action_id)
    {
        global $db, $smarty;
    
        $sql = "SELECT * FROM `signups` where `action_id` = '{$action_id}'";
        if (!$result = $db->query($sql)) {
            throw new Exception($db->error);
        }
        $signups = [];
        while ($signup = $result->fetch_assoc()) {
            $signups[] = $signup;
        }
        $smarty->assign('signups', $signups);
    }

     

三、製作樣板

  1. 新增 templates/side_signups.tpl
    {if $op=="show_action"}
      <h2>已報名名單</h2>
      <table class="table table-hover table-striped">
        <thead>
          <tr class="info">
            <th>姓名</th>
          </tr>
        </thead>
        <tbody>
        {foreach $signups as $signup}
            <tr>
              <td>{$signup.uid}</td>
            </tr>
        {/foreach}
        </tbody>
      </table>
    {/if}
  2. 我們在第一行判斷目前 op 狀態,只有在 show_action 時才顯示。

  3. 由於目前我們沒讀出姓名,暫時先顯示uid編號即可。

四、連結樣板

  1. 修改 index.tpl 將剛剛做的樣板連結上來
    <h2>Hello {$name}!</h2>
    {if $group}
      {include file='side_tools.tpl'}
      {include file='side_signups.tpl'}
    {else}
      {include file='side_login.tpl'}
    {/if}

五、練習

  1. 關於 side_signups.tpl 的顯示,是否可以改成判斷有無 $action_id 或者有無傳來 $signups 才顯示呢?其實是不妥的,答案...請想想。

PHP7 入門研習

13-3 同時讀取兩個資料表

  1. 別鬧了,秀出uid編號哪裡知道是誰呢?連自己編號多少可能都不清楚了啊!
  2. 沒錯,不過,我們報名的資料表並沒有儲存姓名(這不是偷懶,是資料庫的正規化),所以,無法直接秀出姓名
  3. 但我們可以利用 join 的功能,同時讀兩個表,順便取得姓名或其他資料。

一、用join一次讀兩個以上的資料表

  1. 關聯資料表方便的地方就是:一次可以讀取兩個以上的資料表,簡易語法如下:
    SELECT a.* , b.* , c.*
    FROM `資料表1` as a
    JOIN `資料表2` as b ON a.`索引欄位`= b.`索引欄位`
    JOIN `資料表3` as c ON b.`索引欄位`= c.`索引欄位`
    WHERE a.欄位='值' AND ….
    
  2. 「left join」代表以左邊為主,順便到右邊撈撈看有無指定的資料。
  3. 「right join」代表以右邊為主,順便到左邊撈撈看有無指定的資料。
  4. 「join」代表兩邊都要同時有資料,否則該筆資料不會出現。
  5. 以本例而言,讀出報名者編號,順便讀出報名者資料的寫法:
    SELECT a.*, b.* FROM `signups` AS a
    JOIN `users` as b ON a.`uid` = b.`uid`
    WHERE a.`action_id` = '{$action_id}'
  6. 此處用 join,表示一定要有會員資料,否則就不秀出來。

二、修改樣板

  1. 修改 \templates\side_signups.tpl 把 uid 改成 name就可以了,這裡順便加上 Email
    {if $op=="show_action"}
      <h2>已報名名單</h2>
      <table class="table table-hover table-striped">
        <thead>
          <tr class="info">
            <th>姓名</th>
            <th>Email</th>
          </tr>
        </thead>
        <tbody>
        {foreach $signups as $signup}
            <tr>
              <td>{$signup.name}</td>
              <td>{$signup.email}</td>
            </tr>
        {/foreach}
        </tbody>
      </table>
    {/if}

     

PHP7 入門研習

13-4 報名過後,就不可再報

  1. 其實,我們已經悄悄的內建此功能了!為什麼?因為我們當初設計資料表時,就把「uid」和「action_id」組合成主索引,主索引只能有一組,所以絕對不會讓您有機會出現第二筆重複資料。若硬是要報名就會出現「Duplicate entry '2-1' for key 'PRIMARY'」的訊息。
  2. 「那為何還要提出來修改?」因為,您覺得使用者看得懂「Duplicate entry '2-1' for key 'PRIMARY'」是啥意思嗎?更別說當使用者明明報完活動了,卻還在該活動頁面看到「我要報名」的按鈕,這時心裡會如何評價這位開發者呢?
  3. 所以,請修改一下列處,以及活動頁面裡面,偵測,若使用者已經報名,就不顯示「我要報名」的按鈕。
  4. 思考一下,作法有那些?

一、建議作法

  1. 登入時就撈出所有已經參加的資料,存成陣列,並放入session
  2. 列出活動時,只要利用in_array()判斷該活動有無在陣列中即可。
  3. 優點,只多做了一次SQL請求就可以搞定。

二、修改 login() 登入函數

  1. 修改 index.php 中的 login 函數,在登入成功後撈取該使用者所有已報名的活動編號
    //登入
    function login()
    {
        global $db;
        $email = clean_var('email', 'Eamil', FILTER_VALIDATE_EMAIL);
        $pass  = clean_var('pass', '密碼');
    
        $sql    = "SELECT * FROM `users` where `email`='{$email}'";
        $result = $db->query($sql);
        if (!$result) {
            throw new Exception($db->error);
        }
        $data = $result->fetch_assoc();
    
        if (password_verify($pass, $data['pass'])) {
            $_SESSION['group'] = $data['group'];
            $_SESSION['name']  = htmlspecialchars($data['name'], ENT_QUOTES);
            $_SESSION['uid']   = $data['uid'];
            $_SESSION['email'] = htmlspecialchars($data['email'], ENT_QUOTES);
    
            //抓取該使用者已報名的活動編號
            $sql = "SELECT action_id FROM `signups` where `uid`='{$data['uid']}'";
            if (!$result2 = $db->query($sql)) {
                throw new Exception($db->error);
            }
            $_SESSION['uid_signup'] = [];
            while (list($action_id) = $result2->fetch_row()) {
                $_SESSION['uid_signup'][] = $action_id;
            }
    
        } else {
            throw new Exception("登入失敗!");
        }
    }
  2. 我們將活動編號都放入 $_SESSION['uid_signup'] 陣列中

  3. 另外,讀取資料庫的迴圈中若還有讀取資料庫迴圈,其 $result 記得改名,否則第一個迴圈的 $result 會被 第二個迴圈的 $result 給取代。

  4. 由於第二個迴圈只抓了一個欄位,故改用 $result2->fetch_row() 來抓,並利用 list() 做指派,把值放到 $action_id 中。

三、修改 logout() 登出函數

  1. 既然登入多了一組 session,登出時也應該要拿掉才是。
    //登出
    function logout()
    {
        unset($_SESSION['group']);
        unset($_SESSION['name']);
        unset($_SESSION['uid']);
        unset($_SESSION['email']);
        unset($_SESSION['uid_signup']);
    }

四、修改 signup() 報名函數

  1. ,這部份很容易就被忽略,這是指登入後,如果又去報名其他活動,那麼報名的當下,應該要把該活動也加到 $_SESSION['uid_signup'] 陣列中才合理。
    //報名
    function signup($action_id)
    {
        global $db;
    
        $uid = $_SESSION['uid'];
    
        $sql = "INSERT INTO `signups` ( `action_id`, `uid`, `signup_date`)
        VALUES ('{$action_id}', '{$uid}', NOW())";
        if (!$db->query($sql)) {
            throw new Exception($db->error);
        }
        $_SESSION['uid_signup'][] = $action_id;
    }

五、修改主樣板的活動列表

  1. 我們直接在樣板中判斷即可,編輯 list_action.tpl
    {if $group=="admin"}
      <a href="javascript:delete_action({$action.action_id})" class="btn btn-danger btn-xs">刪除</a>
      <a href="admin.php?action_id={$action.action_id}" class="btn btn-warning btn-xs">修改</a>
    {elseif $group=="user"}
      {if $action.action_id|in_array:$smarty.session.uid_signup}
        <a href="index.php?action_id={$action.action_id}" class="btn btn-danger btn-xs">取消報名</a>
      {else}
        <a href="index.php?op=signup&action_id={$action.action_id}" class="btn btn-primary btn-xs">我要報名</a>
      {/if}
    {/if}
  2. 我們利用smarty的變數修飾器,直接使用 php 的 in_array() 函數來判斷目前此活動編號有無在 $_SESSION['uid_signup'] 陣列中

六、修改單一活動頁面下方報名按鈕

  1. 一樣直接在樣板中判斷即可,編輯 show_action.tpl
    <div class="text-center">
      {if $group=="admin"}
        <a href="javascript:delete_action({$action.action_id})" class="btn btn-danger ">刪除</a>
        <a href="admin.php?action_id={$action.action_id}" class="btn btn-warning">修改</a>
      {elseif $group=="user"}
        {if $action.action_id|in_array:$smarty.session.uid_signup}
          <a href="index.php?action_id={$action.action_id}" class="btn btn-danger btn-lg">取消報名</a>
        {else}
          <a href="index.php?op=signup&action_id={$action.action_id}" class="btn btn-primary btn-lg">我要報名</a>
        {/if}
      {/if}
    </div>

PHP7 入門研習

13-5 取消報名

  1. 取消報名實際上就是把 signups 中的一筆報名資料刪除而已。
  2. 還記得之前教的 確認後刪除 嗎?依樣畫葫蘆即可!

一、修改連結並產生相關函數

  1. 一樣先把 show_action.tpl 和 list_action.tpl 中的連結改成這樣:
    <a href="javascript:delete_signup({$action.action_id})" class="btn btn-danger btn-xs">取消報名</a>
  2. 接著修改 templates\sweet-alert.tpl(或者要另外做一個新的也行),多加上一個新的函數

    function delete_signup(id){
      swal({
        title: "確定要取消嗎?",
        text: "取消後就不能參加活動囉!",
        type: "warning",
        showCancelButton: true,
        confirmButtonColor: "#DD6B55",
        confirmButtonText: "是!含淚取消!",
        cancelButtonText: "不...還是繼續參加",
        closeOnConfirm: false
      }, function(){
        swal("OK!取消惹!", "下次有機會再來啦!", "success");
    
        location.href='index.php?op=delete_signup&action_id=' + id;
      });
    }
  3. 重點在於14行,也就是實際會連結到 index.php,並且帶個活動編號過去,至於uid不用特別帶過去,因為session中可以抓得到。

二、做出刪除的函數

  1. 接著到 index.php,先新增一組流程:
    case "delete_signup":
        delete_signup($action_id);
        header("location:{$_SERVER['PHP_SELF']}?action_id=$action_id");
        exit;
  2. 接著實際做出該函數:

    //取消報名
    function delete_signup($action_id)
    {
        global $db;
    
        $sql = "DELETE FROM `signups` WHERE `action_id`='{$action_id}' and `uid`='{$_SESSION['uid']}'";
        if (!$db->query($sql)) {
            throw new Exception($db->error);
        }
        $_SESSION['uid_signup'] = array_diff($_SESSION['uid_signup'], [$action_id]);
    }
  3. 報名了要把該活動加到 $_SESSION['uid_signup'] 陣列中,同樣的,取消報名也要從 $_SESSION['uid_signup'] 陣列中把該編號拿掉。不拿掉會怎樣?不會怎樣,系統確實是刪了資料,但該活動還是會一直出現「取消報名」的按鈕而已。
  4. 所以,我們在第10行利用 array_diff([陣列一], [陣列二]) 來達成刪掉陣列中某個值。

三、關於array_diff()

  1. 這個其實並不是什麼移除陣列內容的函數,而是拿來比對兩個陣列的差異,並將差異的部份傳回一個新陣列。
  2. 所以,假設$_SESSION['uid_signup'] 陣列中有三個活動,依序為 [1, 2, 3],那麼,若是要取消活動 2,我們只要寫成 array_diff([1, 2, 3], [2]),該函數就會傳回陣列 [1, 3],因為這剛好是差異部份,是不是看起來如同 2 被刪除了一般!

PHP7 入門研習

14. 開始處理小細節

  1. 系統的CRUD好寫,但細節才是煩人的開始。
  2. 程式寫法是否正確很容易檢查,但邏輯是否正確周密就要靠經驗累積了。
  3. 這些細節不做可能不會怎樣,只是會造成操作體驗不好而已。
  4. 另外,處理方式千百種,除了講師建議的,可以試著用自己的方法處理掉。

 

PHP7 入門研習

14-1 過期活動不該顯示出來

  1. 過期的活動,還有關閉的活動,其實都不該顯示出來。
  2. 問題是,不顯示出來,管理員如何進入已關閉的活動再將之開啟呢?
  3. 特地為管理員做個界面?也是可以,但也太累人了...
  4. 建議解法:列出活動時,可根據身份來呈現不同結果。

一、修改列出活動的函數

  1. 這部份的改法相當簡單,只要修改一個函數就結束了。
    //列出所有活動
    function list_action()
    {
        global $db, $smarty;
        $where  = (!isset($_SESSION['group']) or $_SESSION['group'] != "admin") ? 'where `end_date` > now() and `enable`=1' : '';
        $sql    = "SELECT * FROM `actions` $where order by action_date desc";
        $result = $db->query($sql);
        if (!$result) {
            throw new Exception($db->error);
        }
        $actions = [];
        while ($values = $result->fetch_assoc()) {
            $actions[] = $values;
        }
        $smarty->assign('actions', $actions);
    }
  2. 第五行的條件中,我們設了兩個條件,一個是尚未登入者(只要判斷 $_SESSION['group'] 不存在即可),另外一個是已登入,但身份不是管理員者,只要符合其一,就加入截止時間必須大於現在時間(尚未截止之意),以及活動是開啟狀態的條件。

PHP7 入門研習

14-2 新增分頁

  1. 目前活動少,所以不分頁也沒什麼關係,但未來活動一多時,沒分頁可就麻煩了。
  2. 分頁可以自己寫,但麻煩,直接套用現成物件會更簡單~

一、在列表函數中加入分頁

  1. 下載 PageBar fot bootstrap 分頁物件
  2. 下載解壓到 class/PageBar.php
  3. 修改 index.php 中的 list_action() 函數:
    //列出所有活動
    function list_action()
    {
        global $db, $smarty;
        $where = (!isset($_SESSION['group']) or $_SESSION['group'] != "admin") ? 'where `end_date` > now() and `enable`=1' : '';
        $sql   = "SELECT * FROM `actions` $where order by action_date desc";
        include_once "class/PageBar.php";
        $PageBar = getPageBar($db, $sql, 5, 10);
        $bar     = $PageBar['bar'];
        $sql     = $PageBar['sql'];
        $total   = $PageBar['total'];
        $result  = $db->query($sql);
        if (!$result) {
            throw new Exception($db->error);
        }
        $actions = [];
        while ($values = $result->fetch_assoc()) {
            $actions[] = $values;
        }
        $smarty->assign('actions', $actions);
        $smarty->assign('bar', $bar);
        $smarty->assign('total', $total);
    }
  4. 必須放在原有的 $sql 和 $result 之間才行!
  5. getPageBar的參數
    getPageBar($mysqli, $sql, $show_num = 20, $page_list = 10, $to_page = "", $url_other = "")
    1. $mysqli:使用的資料庫物件名稱(必要)

    2. $sql:欲執行的語法(必要)

    3. $show_num = 20:每頁顯示資料數

    4. $page_list = 10:分頁工具列呈現的頁數

    5. $to_page = "":要連結到那一個頁面

    6. $url_other = "":其他額外的連結參數

  6. 修改 templates/list_action.tpl 樣板,在標題處加入資料數:
    <h2>活動列表<small>(共 {$total} 個活動)</small></h2>
  7. 在最下面加入分頁工具列:
    {$bar}
    
  8. 大功告成!

PHP7 入門研習

14-3 個資保護

  1. 其實這個系統沒有個資問題
  2. 不過,在這個不理性的時代,還是得做做樣子來杜攸攸之口。
  3. 本系統要處理的大概就是已報名名單的部份。
  4. 名字若是三個字的,中間用O來取代,Email部份,僅留 @ 左邊帳號部份。

一、修改 list_signup() 函數

  1. 我們的名單是在 index.php 中的 list_signup() 函數中產生的,所以,從源頭動手腳(從樣板其實也可以,但麻煩了些)。
    //已報名名單
    function list_signup($action_id)
    {
        global $db, $smarty;
    
        $sql = "SELECT a.*, b.* FROM `signups` AS a
        JOIN `users` as b ON a.`uid` = b.`uid`
        WHERE a.`action_id` = '{$action_id}'";
        if (!$result = $db->query($sql)) {
            throw new Exception($db->error);
        }
        $signups = [];
        while ($signup = $result->fetch_assoc()) {
            if (!isset($_SESSION['group']) or $_SESSION['group'] != 'admin') {
                //姓名中字取代
                $len = strlen($signup['name']);
                if ($len > 6) {
                    $target         = substr($signup['name'], 3, 3);
                    $signup['name'] = str_replace($target, '○', $signup['name']);
                }
                //email取代
                preg_match("/(.+?)\@(.+?)\.(.+?)$/", $signup['email'], $em);
                $signup['email'] = "{$em[1]}@" . str_repeat("x", strlen($em[2])) . ".{$em[3]}";
            }
            $signups[] = $signup;
        }
        $smarty->assign('signups', $signups);
    }

二、中文字的取代

  1. 先看姓名部份,首先,要兩個字以上才處理,故用 strlen('文字') 來抓姓名長度,一個 UTF-8 中文字是 3 byte,所以,只要超過 6,就代表姓名是三個字以上。
  2. substr('文字', 起始位置, 長度) 是用來抓取部份字串的,第二個參數是抓取的起始位置,第一個中文字位置是 0~2,第二個字就是從 3~5。故起始位置設為 3,第三個參數則是抓取長度,剛剛說了,一個中文字 3 byte,所以,設成 3 代表抓一個中文字。如此設定下來,就是抓出第二個字的意思,以「吳弘凱」來說,抓出來的就是「弘」。
  3. str_replace('搜尋', '取代', '文字') 則是用來取代字串的,我們把找到的第二個中文字,用個其他符號取代之。

三、Email的取代

  1. preg_match('/正規表達式/', '文字' , $結果) 這個用法就比較高深一點,首先您要給它一個正規表達式,然後,該函數會去搜尋第二個參數中,符合這個正規表達式的內容有哪幾個,然後依序存入「結果」中。
  2. $結果[0]是原字串,$結果[1]則是第一個符合正規表達式的字串,依此類推。
    • . 表示任意字元(不包括換行符號)
    • + 修飾前面一個字元,限定數量是一個或者多個,但預設會儘可能匹配更長的字符串(貪婪匹配)
    • + 後面再來個 ? 就表示用非貪婪匹配。舉個例子:假設字串 「*abc*abc*」, 如果有 ?,那麼匹配到的就是 *abc*,如果沒有,匹配到的就是整個字串,因為 . 也可以匹配到 *.
  3. 至於完整正規表達式...就留給外星人去解讀吧。
  4. str_repeat('字串', 次數) 是用來重複字串的用,我們把@右邊的網域,例如 gmail 取代成 xxxxx 這個樣子。

 

PHP7 入門研習

14-4 刪除活動得同時刪除報名者

  1. 其實這個超簡單的
  2. 只是常常寫了活動的刪除,也寫了報名者的刪除,卻往往忘了刪活動時也要順便刪除報名者。
  3. 所以,這不是會不會的問題,而是有沒有注意到的問題。

一、修改刪除活動函數

  1. 修改 admin.php 中的 delete_action()
    //刪除活動
    function delete_action($action_id)
    {
        global $db;
    
        $sql = "DELETE FROM `actions` WHERE `action_id`='{$action_id}';";
        if (!$db->query($sql)) {
            throw new Exception($db->error);
        }
    
        $sql = "DELETE FROM `signups` WHERE `action_id`='{$action_id}'";
        if (!$db->query($sql)) {
            throw new Exception($db->error);
        }
    }

     

PHP7 入門研習

14-5 寄發通知信

  1. 基本上,在windows下寄信得先設定一下。

一、UniForm Server的寄信設定

  1. 如果您用的是XUniForm Server,我們可以透過Gmail來讓網站可以寄信。
  2. 您可以開啟控制台,選擇「General→Mail utility msmtp」來進行寄信設定。
  3. 開啟後,右邊下拉選單可先選擇「Gmail」,然後點選「Edit configuration File」來編輯設定檔。
  4. 開啟後,找到Gmail的設定部份,請如下圖輸入您的Gmail帳號及密碼(請將中文字的部份以實際資料替換掉)
  5. 接著拉到檔案最下方,確認預設用的是Gmail來寄信(若不是,請手動修改之)
  6. 最後請按「Ctrl+S」來儲存設定檔。
  7. 然後可以透過這個設定畫面來測試寄信功能是否正常:
  8. 順利的話,應該就可以收到信件了!
  9. 實際使用時,Google會要求 [允許安全性較低的應用程式] 設定處於啟用狀態,如此才收得到信。

二、寄信函數

  1. 在PHP中,寄信的函數是mail(),不過要寄出一封可以支援網頁語法的信需要較多設定,所以,我們自己另做一個函數(放在 fintion.php 中):
    //寄信
    function send_mail($to, $subject, $message, $from)
    {
        if ($to and $subject and $message) {
            $headers = "MIME-Version: 1.0\r\n" .
                "Content-type: text/html; charset=utf-8\r\n" .
                "From: {$from}\r\n";
    
            mail($to, $subject, $message, $headers);
        }
    }
    
  2. 其中 $headers 就是告知收信軟體這封信是有支援網頁語法的檔頭。

  3. 最後,只要在報名完,順便寄個通知信即可:

    //報名
    function signup($action_id)
    {
        global $db;
    
        $uid = $_SESSION['uid'];
    
        $sql = "INSERT INTO `signups` ( `action_id`, `uid`, `signup_date`)
        VALUES ('{$action_id}', '{$uid}', NOW())";
        if (!$db->query($sql)) {
            throw new Exception($db->error);
        }
    
        $sql    = "SELECT * FROM `actions` where `action_id` ='{$action_id}'";
        $result = $db->query($sql);
        if (!$result) {
            throw new Exception($db->error);
        }
        $action = $result->fetch_assoc();
    
        $_SESSION['uid_signup'][] = $action_id;
    
        $admin_email = "tad@tn.edu.tw";
        $message     = "<div>{$_SESSION['name']} 您好:</div><p>您已成功報名「{$action['title']}」活動。</p>";
        send_mail($_SESSION['email'], "「{$action['title']}」已報名通知", $message, $admin_email);
        send_mail($admin_email, "「{$action['title']}」已報名通知", $message, $admin_email);
    }
  4. 其中,我們用了 $_SESSION['email'],但實際上此變數不存在,所以,我們在login()登入時順便加個 $_SESSION['email']:

    if (password_verify($pass, $data['pass'])) {
        $_SESSION['group'] = $data['group'];
        $_SESSION['name']  = $data['name'];
        $_SESSION['uid']   = $data['uid'];
        $_SESSION['email'] = $data['email'];
    
        //抓取該使用者已報名的活動編號
        $sql = "SELECT action_id FROM `signups` where `uid`='{$data['uid']}'";
        if (!$result2 = $db->query($sql)) {
            throw new Exception($db->error);
        }
        $_SESSION['uid_signup'] = [];
        while (list($action_id) = $result2->fetch_row()) {
            $_SESSION['uid_signup'][] = $action_id;
        }
    
    } else {
        throw new Exception("登入失敗!");
    }
  5. 當然,登出時也記得取消之

    //登出
    function logout()
    {
        unset($_SESSION['group']);
        unset($_SESSION['name']);
        unset($_SESSION['uid']);
        unset($_SESSION['uid_signup']);
        unset($_SESSION['email']);
    }

     

 

 

 

 

PHP7 入門研習

15. 我參加的活動

一、練習

  1. 對,您沒看錯,這個請自己寫....
  2. 當初有設計一個「我參加的活動」功能,也就是要列出該使用者所有有報名過的活動,請將之做出來吧。