PHP 實例引導

李忠憲 2007/6/28初稿

2010/02/09增補

認識程式語言

程式語言是一種人工語言,與自然語言一樣有文法和語意解釋上的規定。由於是人工制定的語言,它的結構比較嚴謹,包含資料結構與程式流程控制兩個主要成分。程式語言的分類,從歷史來區分共有四代,從第一代的 機器語言、第二代的組合語言、到第三代的結構化語言,目前的第四代語言被稱為人工智慧語言。

從應用層面來區分可分為低階、中階、高階語言,越低階的語言越容易被機器解讀,其執行效率也越高,越高階的語言越抽象,越容易被人類閱讀和理解,跟自然語言也越接近,但比須經過翻譯才能讓機器執行。

機器語言位於最低階,這種語言是設計給機器判讀的,執行效率高但不利於閱讀,隨著不同 CPU 的出現,並發展出微控語言。微控語言可視為機器語言的子集合,它比機器語言更低階,可以直接控制 CPU 的處理流程,能將機器語言在執行時期最佳化。組合語言是人類比較容易閱讀的低階語言,一般在開發驅動程式時會使用到組合語言,它比須被編譯成機器語言才能執行。

中階語言是指同時具有低階語言直接控制硬體的能力,同時又具有高階語言結構,容易撰寫閱讀的語言,例如:fortran、C。高階語言則有 BASIC、PASCAL、JAVA......等語言。

程式語言的分類還可以從執行方式來區分,包括:直譯式語言、編譯式語言、中介碼語言、標記式語言、指令稿語言、藍圖語言等,說明如下:

直譯式語言:程式以純文字檔來儲存,當要執行時由直譯器即時翻譯成機器語言,例如:BASIC。主要問題為執行效率較差,程式內容無法保密。好處是分享、修改很容易,PHP 就是直譯式語言。

編譯式語言:程式必須事先使用編譯器翻譯成機器語言,並儲存成可執行檔,例如:C。執行時不用翻譯所以效率高,原始程式碼不易取得,也因此會阻礙分享,修改程式碼後必須重新編譯後才能測試。

中介碼語言:程式碼編譯成中介碼後儲存,中介碼最大的好處是可以跨越平台,但執行時還需要由虛擬機器(VM)將中介碼代換成該平台認識的機器語言才能執行,例如:JAVA。有些中介碼語言,並不提供虛擬機器,而是提供轉碼器,程式寫好後可以利用轉碼器,轉換成其它語言,利用該語言的機制來執行程式。它不但能跨越硬體平台的限制,也能跨越軟體平台的限制。例如:JSON。

標記式語言:標記式語言嚴格說來並非是程式語言的一種,因為它更為抽象,而且無法執行。標記式語言通常是一種語意表徵語言,它有自己的文法和語意結構,但只能用於表達資料的結構和意義(有些語言還會描述外觀),需要依靠剖析器來處理,並需要特定應用程式才能執行。例如:HTML、Postscript(印表機用)。在 XML 中指令稿語言將被包含在標記中,當成標記語言的子集合來處理。

指令稿語言:使用於瀏覽器用來提供互動的語言。必須依附於瀏覽器才能執行,只能操控瀏覽器本身提供的有限功能,程式與硬體無關不需翻譯成機器語言,也不需要編譯成中介碼。 如果把瀏覽器當成網路環境的作業系統,則指令稿語言就是直譯式語言的一種,如果把瀏覽器當成應用程式,指令稿語言則近似於標記式語言。

藍圖語言:藍圖語言嚴格來說並非是程式語言的一種,因為它無法執行,算是程式設計前的一種系統分析工具,例如:UML。某些提供 UML 的軟體還會附上轉碼器,讓您從 UML 直接輸出成各種不同語言的程式碼。

型態理論

程式運作時,必須將輸入資料經過運算處理後再輸出,通常輸入的資料會暫時使用變數來保存,以等候運算處理。隨著程式的目的不同,資料也區分為數值資料(又區分為整數、浮點數)、文字資料、二進位資料、邏輯值......等等。也就是說,變數會有各種不同的型態。

不同程式語言看待變數型態的觀點也不同,主要可分為型態變數,及無型態變數兩種。前者在使用變數時,變數的值依其型態不同會有不同的解讀方式,後者則把所有變數都視為二進位資料來處理,如何解讀變數值是程式設計者自己要去賦予資料意義,與變數的運算過程無關。絕大多數的語言都屬於型態變數語言,只有少數低階語言是屬於無型態變數語言。

型態變數語言從變數宣告來區分,可分為事先宣告與不用宣告兩種不同主張。變數事先宣告的用意在於讓程式在編譯階段就將每個變數所需的記憶體空間決定好,這樣在程式執行過程中不會有額外的記憶體需求,以免因系統資源的不足造成程式中斷。如果變數不用宣告,表示變數所使用的記憶體空間在執行階段才去計算,能有多少系統資源就用多少系統資源,程式能適應不同等級的電腦硬體,發揮最大的效能。前者如 PASCAL、C,後者如 Python、Perl。

從變數型態的轉換來說,又可分為強型態與弱型態兩種不同主張。強型態變數其型態一旦決定以後就無法再改變;弱型態變數則會依據運算子自動轉換型態。有些語言雖然採用強型態,但也允許使用形態轉換運算子來變更變數型態,算是兼具強型態與弱型態的特例。通常需要事先宣告變數的多屬於強型態,不用宣告變數的多屬於弱型態,但是也有例外(C++為弱型態,但需要事先宣告)。

程式語言的文法結構

程式語言的文法結構比自然語言更為嚴謹,但變化也比較有限。一般使用底下的術語來描述文法:

syntax 的組成要素
keyword 保留字,在文法結構中有特定含意的字,不可以拿來命名。
variable 變數,程式中為了暫存運算結果,可以由設計者自訂一個字來代表該結果,這個字稱為變數。變數與保留字是互斥的。
operator 運算子,即進行各種運算用的數學符號。
control structure 控制結構,由保留字依照一定的規則組合而成,用來控制程式的執行流程。
data structure 資料結構,由變數組合而成的一種特殊結構,通常為了演算的方便,使用資料結構是不可避免的。
expression 運算式,用來進行運算的程式片斷,可分為邏輯、數值、符號運算式。運算式必然包含 variable(或data structure)和 operator。
condition 判斷式,也就是上面說過的邏輯運算式。
statment 敘述,由 keyword 和 experssion 依照 control structure 所組成的程式片斷
在 PHP 中合法的變數名稱必須是以英文字母開頭的文字、數字或符號組合,且不可以使用保留字。變數名稱前面必須冠上錢號【$】。合法的敘述則必需以分號【;】作為結尾,敘述一行寫不下時可以分為數行,只要最後一行的結尾寫上分號,程式就不會解讀錯誤。以下為 PHP 語言中 foreach 敘述的文法:
foreach (array_expression as variable)
    statement

 

學習 PHP 曲線

PHP 是一種伺服端描述語言,用來製作動態網頁。當一個網客開啟網頁時,伺服端便會處理 PHP 指令,然後把其處理結果送到網客的網頁瀏覽器上面。PHP 是開放原始碼 (Open Source) 而且是跨平台的。PHP 可以在 Windows NT 以及很多不同的 Unix 版本執行,它也可以被建成一個 Apache 模組,或者是一個 CGI 的二元檔案(binary)。PHP 預設會以 Apache 模組的方式運作,在這種模式下 PHP 是特別的輕巧而快速,它將常駐於記憶體中分享Apache的定址空間,因此可以避免反覆的IO讀取,讓程式的表現更有效率。

PHP 除了能夠用來產生你的網頁內容之外,也可以用來傳送 HTTP 表頭(URI):你可以利用 HTTP 表頭設定 cookies、進行身分驗證以及將使用者重新導向至新的頁面。它也提供了優良的資料庫連結功能(透過 ODBC 的連結功能可以把各種檔案,如:CSV、XLS、MDB......,當成資料庫來使用),另外還有提供各式各樣的外部函式庫(library),可以讓你用來做幾乎所有的事情,上至產生 PDF 文件,下至解析 XML。

PHP 雖然在語法上延續 C 和 Perl 的格式,但是因為牽涉到網頁設計,您還得熟悉一套 Client Script。當然如果您已經非常熟悉 C 的話,對於撰寫 PHP 程式將會有不小的幫助,然而 PHP 尚需要其他方面的基礎,以下的列表就是學習 PHP 應有的基礎:

至於更進一步的應用,則必須學習更多新的課題,例如: OOP 物件導向設計、MVC 開發框架、Flash+AMF、Ajax.....等等。

請先閱讀 動態網頁程式設計 學習怎樣利用 JavaScript 控制網頁、檢查表單傳值的內容。

請先閱讀 建置Apache伺服器 設定 Apache 預設語系與字元集,學習虛擬資料夾與虛擬站臺的管理,網頁身份驗證與其它進階管理技巧。

如果想要在 Windows 平台所提供的 IIS 伺服器上執行 PHP,你可以閱讀 IIS上結合ASP、PHP建置Web2.0資源分享系統平台

請先閱讀 php.ini 了解設定PHP執行環境的方法,啟用開發系統所需要的模組。

由於開發網頁應用程式需要先設計網頁,建議您要有一套好用的網頁設計軟體,Expression Web(FrontPage)應該就夠用了,或者是使用免費的 NVU,windows 免安裝版Linux GNU版

另外,需要有 PHP 的開發軟體能辨識指令敘述並用不同顏色標示以方便除錯,同時必須能將程式儲存成 utf-8 編碼,建議使用 editplus 2(商業軟體)或是 pspad(自由軟體)。

開發程式過程中,需要有一本函式庫字典在手邊備查,如果要離線攜帶可以從這裡下載:PHP4函式庫,如果是在網路環境下,建議連到官方網站去查閱最新的 PHP5 函式庫,網址是: http://www.php.net/manual/

除了本文提供的諸多範例外,PHP 世界多的是自由軟體可以下載研究,這邊有人蒐集了不少教學用範例,由於筆者沒有閱讀過所有的內容,不敢擔保資料是否正確,有興趣的學員可以當成參考資料閱讀,請從這裡下載:PHP4 程式範例

動態網頁概念

DHTML是 Dynamic HTML 的簡稱,相較於 HTML 它更強調網頁的動態表現,所謂動態網頁即是指:網頁在瀏覽器中能依據參觀者的操作或時間狀態的變化適時地改變網頁內容。

由於 HTML 僅能依靠標記語法來展現網頁內容,並沒有動態改變的能力,因此必須將網頁內容物件模型化(DOM)接著透過 Script 語言來操控它的內容,另外再結合事件驅動機制以便反映參觀者的操作,同時為了讓動態網頁能展現更精彩的視覺變化,又加上了串接樣式表(CSS:Cascading Style Sheet)的支援,如果用公式來表示,可以寫為:

DHTML = DOM + Events + Script + CSS

網頁應用程式被大量運用後,大多的使用者還是比較不適應它的操作模式,由於網頁連結經常跳躍轉移容易迷失、操作回饋不及時必須等待伺服器回應......等缺失。因此 RIA(Rich Internet Application) 程式設計理念被提出來,希望網頁上的應用程式能像桌面應用程式一樣好用。

RIA 程式設計目前有兩種選擇,包括:flash-base 和 javascript-base。因應前者的需求出現了 AMF-PHP 解決方案,後者則是 Ajax 解決方案。

左圖為 Ajax 的概念圖,如果把 Javascript call 改成 action script call,把 HTML+CSS data 改成 XML data,把 Ajax engine 改成 Flash,就變成 flash-base RIA 的概念圖。
RIA 的目的是要改變使用者對網頁應用程式的觀感,把使用桌面應用程式的使用經驗遷移到網頁來。這個使用經驗簡單的說,就是:
  • 即時運算
  • 運算過程的可見度(視覺化)
  • 人機互動中的自動程序(例如:定時存檔、事件通知音效、表單欄位即時檢查)
  • 與其他桌面應用程式互動

 

 

如何寫 PHP 程式

當開發 PHP 程式時,必須將程式寫在 <? 和?> 符號之間,以便與 HTML 語法區隔,底下這個寫法是採用傳統程式設計方法所寫的 PHP 程式:

<?
    $message="Hello PHP";
    echo "<html><body>".$message."</body></html>";
?>

在 PHP 程式中使用變數必須在變數名稱前面加上【$】符號,運算符號【.】寫在字串中間時是表示字串連接的意思。echo 指令將字串輸出到網頁上。每一行程式都使用【;】結尾。

傳統程式設計方法用程式來處理 HTML 原始碼,這種寫法將造成網頁內容無法在編寫程式時預覽,並不利於網頁應用程式的開發,因此最好是改用嵌入式(online)寫法,因為這樣寫出來的程式才可以使用網頁編輯器來排版:

<? $message="Hello PHP"; ?>
<html>
   <body>
   <?=$message?>
   <body>
</html>

使用嵌入式寫法有一點要特別注意,就是當嵌入式語法中只包含一行程式時,可以使用【=】符號來代替 echo 指令,習慣上不寫結尾的【;】。如果是一行以上,必須遵照傳統設計方法的規則來撰寫。

以下幾種 <??> 符號的使用方法,都是合乎語法規定可被接受的:

<? echo "我來了!"; ?>
<? echo "我來了!";
 ?>
<?php
   echo "我來了!";
?>

 

PHP 的數值型態

常數

一旦定義以後就不能改變內容的稱為常數,習慣使用大寫識別字表示:

<?
Define("BLUE","#0000FF");
Define("HI","Hello, PHP!");
?>
<font color="<?=BLUE?>"><?=HI?></font>

產生如下結果
Hello, PHP!

Defined() 可用來判斷常數是否存在!

Defined(“BLUE”) //true

變數

變數是區分大小寫的,而內建函式與敘述句則不區分大小寫!變數並不需要像常數一樣在使用前要先定義,當使用指定運算子(=)時,變數就自動被建立了!

$price=10;
$num=5;
$total=$price * $num;

PHP使用弱型態變數,變數的型態由變數值來自動決定:

$a=12; //整數
$a=0.24; //浮點數
$a="A"; //字串

環境變數

PHP 可以讓你把環境變數當作一般變數使用。所謂環境變數,包含:

$_ENV 作業系統環境設置(搜尋路徑、電腦名稱、當前登入者帳號......)
$_SERVER Apache 伺服器為執行 CGI 程式所設定的環境變數
$_COOKIE 瀏覽器送來的參觀者暫存訊息
$_GET 參觀者在網址列輸入或超連結所觸發的 URL 字串
$_POST 參觀者透過網頁表單所輸入的字串
$_FILES 參觀者上傳的檔案
$_SESSION 參觀者連線後所記錄的暫存變數,儲存於伺服器上。
$_REQUEST $_GET+$_POST+$_COOKIE 的所有變數
$_GLOBALS 以上所有變數的總和

對於網頁應用程式的開發來說,我們比較關心的是 $_SERVER 環境變數。下表為常用變數列表:

'REQUEST_METHOD' 參觀者以何種方式連上此網頁:GET、HEAD、POST或PUT
'REQUEST_TIME' 參觀者送出連線要求時的時間,運用此變數可以算出參觀者所在地的網路頻寬
'HTTP_REFERER' 參觀者從何處連上此網頁,此變數可以取得網址,如果參觀者是直接從網址列輸入,則為空字串。
'HTTP_USER_AGENT' 取得參觀者所使用的瀏覽器類型及版本
'REMOTE_ADDR' 取得參觀者的 IP 位址
'REMOTE_HOST' 取得參觀者註冊在 DNS 中的主機名稱
'REMOTE_PORT' 取得參觀者連線時所用的 port 號
'SCRIPT_FILENAME' 取得此網頁在硬碟上的絕對路徑
'SCRIPT_NAME' 取得此網頁的網址中路徑與檔名的部份(絕對網址),當程式需要遞迴呼叫時很有用
'REQUEST_URI' 取得參觀者所輸入的網址,真正起作用的網頁。例如:輸入【http://www.domain.com/】,真正傳輸的是【/index.php】
'PHP_AUTH_USER' 參觀者透過網頁身份驗證機制所輸入的使用者名稱
'PHP_AUTH_PW' 參觀者透過網頁身份驗證機制所輸入的密碼

利用 $_SERVER['HTTP_REFERER'] 環境變數,我們可以保護特定網頁,禁止從別的站台盜連:

<?
$myurl=substr($_SERVER['HTTP_REFERER'],0,25);
$myurl=strtoupper($myurl);
if ($myurl!="HTTP://WWW.MEPS.TP.EDU.TW") exit;
?>
.............受到保護的網頁內容..............

上面程式中使用 exit 敘述來中斷網頁輸出,這使得後面的網頁內容通通看不到。

如果要保護網頁僅提供給校內連線,則可以利用另一個環境變數 $_SERVER['REMOTE_ADDR']:

<?
$myip=substr($_SERVER['REMOTE_ADDR'],0,7);
if ($myip!='172.16.') {
    header
("HTTP/1.0 404 Not Found");
    exit;
}

?>
.............受到保護的網頁內容..............

上面程式中使用 header() 函式來強迫 Apache 回應網頁找不到的錯誤訊息,這能避免參觀者看到空白網頁而感到迷惑,同時又能偽裝網頁被保護的原因。有關 header() 函式的其它功能,在下文介紹。

更詳細的環境變數表列,可以使用底下的程式取得:

<? phpinfo(); ?>

字串轉換(string conversion)

把字串拿來進行數值運算,則字串轉換會自動發生,但字串內容並不會改變:

$str = "222B 貝克街";
$x =$str + 3;
Echo $x; //225
Echo $str; //222B 貝克街

字串轉換只會轉換字串開頭的數字,遇到文字時則忽略不處理!除了轉換為整數外,如果字串格式匹配,也會自動轉換為浮點數或負數。

$pen = "10 PENS";
$water = "0.5 CUP";
$total = $pen + $water; //$total = 10.5

字串的串聯(concatenation)則是用【.】運算子;其他有關數字的運算符號就如同你所預期的一樣:

<?
$greeting = "Hello ";
$num = 3 + 2;
echo $greeting.$num++."people!";
echo $greeting.++$num."people!";
?>

會產生如下結果
Hello 5 people!
Hello 7 people!

PHP 有全套的運算元(operator),它們的運作方式就如同你所預期的一樣 -- 特別是當你有 C 或者 C++ 的知識背景時。使用 PHP 的最高指導原則:「有疑問時,先試看看;你可能會成功。」

正如 Perl 一樣,一個字串用雙引號括起來,會使得其中的變數被置換(interpolate),而如果以單引號括起來,則變數不會被置換。因此,

<?
$name = 'Susannah';
$greeting = "Hello, $name!";
echo "$greeting\n";
echo '$greeting\n';
?>

產生如下結果
Hello, Susannah!
$greeting\n

注意一下,字串中 \n 字元是換行符號,就跟 Perl 或 C 一樣。不過這只在以雙引號括起來的字串內才有效。其它常用脫逸字元還有:

\r enter 鍵,WINDOWS 的文件換行使用\r\n,Linux 的文件則使用\n
\t tab 鍵
\xhh 將 hh 所代表的數字當成16進位內碼來處理,\x20 代表ASCII 32,也就是空白鍵。這個功能常用來處理特殊字元。
一般用途中,\會將後面連接的字元直接印出不處理。例如:

echo "\""

產生之結果為
"

類型竄改(type juggling)

PHP 對於兩個不同型態的變數進行運算時,會根據運算元自動進行類型竄改:

$a = 1; //$a 為整數
$b = 2.3; //$b 為浮點數
$c = $a + $b; //$a 自動轉為浮點數
$d = $c . "dollar"; //$c 自動轉為字串

類型塑造(type casting)

如果擔心類型竄改最後不能求取想要的結果,PHP 還允許指定變數類型:

$a = 1;
$b = 2.3;
$c = $a + (int) $b; //$c = 3 而非 3.3

除 (int) 外還可以進行下列各種類型塑造:

(double) (float) (real) //結果都是雙精度數值
(string)
(array)
(object)

可變變數

PHP 允許使用變數當成變數名稱,底下就是一個例子:

$cat = "fish";
$dog = "cat";
$fish = "$dog";
$$fish = "dog";
$$cat = "fish";

運算的結果:

$cat -> dog
$dog -> fish
$fish -> cat

參照運算子

PHP 允許使用【&】運算子來參照變數所在的位址,這與使用 C 語言的【*】指標運算子有異曲同工之妙:

$cat = "fish";
$dog = &$cat;
$dog = "cat";
echo $cat; // cat

& 通常應用在傳遞參數給函式時,也就是所謂傳址呼叫。請比較底下的兩支程式,左邊為傳址呼叫,右邊為傳值呼叫:

function inc($x,&$y) {
    $y += $x;
}

$a=$b=10;
inc($a,$b);
echo $a." ".$b; // 10 20

function inc($x,$y) {
    return ($y += $x);
}

$a=$b=10;
$b=inc($a,$b);
echo $a." ".$b; // 10 20

右邊的範例程式使用傳值呼叫,傳值呼叫事實上是將 $a 和 $b 的變數值拷貝給函式中的另外兩個變數 $x 和 $y,因此在 $y 計算完畢後,必須使用 return 敘述將 $y 的變數值傳回給 $b 變數。

左邊的範例程式使用傳值呼叫將 $a 的值拷貝給 $x,使用傳址呼叫將 $y 建立成 $b 變數的別名,也就是說 $y 其實就是 $b,因此程式計算完畢後不需要將變數值傳回。

變數的生命週期

變數是在程式執行時期所建立的,只存在於暫存記憶體中,因此其存活時間是有限的,除非使用檔案或資料庫來永久保存。通常變數的生命週期是由第一次建立變數時的位置來決定,在上例中,inc() 自訂函式中所使用的變數 $x 和 $y 只存活於該函式中,稱為 local 變數,在函式範圍以外的地方,無法存取這兩個變數。主程式也使用到兩個變數 $a 和 $b,由於變數 $a 和 $b 是在主程式中建立的,因此被視為 global 變數。

由於函式對於變數具有遮斷作用,因此底下的程式中,$a 和 $b 變數有兩對,位於 inc() 函式中的是 local 變數,位於主程式中的是 global 變數,兩對變數應視為不同變數。

function inc($a,$b) {
    $b += $a;
    echo $a." ".$b;
}

$a=$b=10;
inc($a,$b); //10 20
echo $a." ".$b; // 10 10

如果要在函式中直接存取主程式所宣告的 global 變數,則必須在變數名稱前加上 global 宣告,如下:

function inc() {
    global $a,$b;
    $b += $a;
}
$a=$b=10;
inc();
echo $a." ".$b; // 10 20

陣列(array)

PHP 的陣列與 Perl ㄧ樣區分為一般陣列和關聯性陣列兩種,前者的索引值是數字,後者則是字串。在 Perl 語言中稱呼略有不同,前者稱為陣列,後者則稱為雜湊(hash)。PHP 與 Perl 相同都使用方形括弧【[]】來指定陣列的索引:

$fruit[0] = 'banana';
$fruit[1] = 'papaya';
$favorites['animal'] = 'turtle';
$favorites['monster'] = 'cookie';

如果你授與陣列一些值,但是索引是空白的,PHP 會把這個值所代表的物件設定到陣列的尾端。上面所列有關 $fruit 這個變數,以下面的方式授與變數值,其結果是一樣的:

$fruit[] = 'banana';
$fruit[] = 'papaya';

使用多維陣列時,每一維均使用一對方形括弧來表示,不可以寫在一起:

$people['David']['shirt'] = 'blue';
$people['David']['car'] = 'minivan';
$people['Adam']['shirt'] = 'white';
$people['Adam']['car'] = 'sedan';

建立陣列的方式除了直接使用【=】assign 運算子,也可以利用 array() 函數來建立:

$fruit = array('banana','papaya');
$favorites = array('animal' => 'turtle',
                   'monster' => 'cookie);

多維陣列你可以使用底下的方式來建立:

$people = array ('David' => array('shirt' => 'blue',
                                  'car' => 'minivan'),
                 'Adam' => array('shirt' => 'white',
                                 'car' => 'sedan')      );

 

PHP 的控制結構

條件分支

你可以利用控制結構 if、else 以及 elseif 來設計分支程序:

if ($myweight>($myheight-100)*0.8) {
   $mymsg="體重過重,該減肥了";
} elseif ($myweight<($myheight-100)*0.8) {
   $mymsg="體重過輕,多吃點";
} else {
   $mymsg="標準身材";
}

在多重分支的場合,還可以使用 switch 來撰寫程式,程式更容易閱讀:

<?
$myweekday=getdate();
switch ($myweekday[wday]) {
    case 0:
        $mystr="日";
        break;
    case 1:
        $mystr="一";
        break;
    case 2:
        $mystr="二";
        break;
    case 3:
        $mystr="三";
        break;
    case 4:
        $mystr="四";
        break;
    case 5:
        $mystr="五";
        break;
    case 6:
        $mystr="六";
        break;
}
?>今天是星期<?=$mystr?>

使用 switch 時在 case 與 case 之間會以由上而下的方式逐行執行,一直到出現 break 敘述的地方才停止,因此利用這個特性可以設計出比較特別的分支結構:

switch ($myweekday) {
    case 0:
    case 6:
        echo "今天不上班,不用簽到!";
        break;
    case 1:
    case 2:
    case 3:
    case 4:
    case 5:
        echo check_pass();
}

switch 條件判斷中的例外處理,可以使用 default 敘述:

switch ($myweekday) {
    case 0:
    case 6:
        echo "今天不上班,不用簽到!";
        break;
    default:
        echo check_pass();
}

當判斷條件複雜難以簡化成幾個 case 的情形下,也可以把 if 和 switch 結構混合使用,底下的範例在 PHP 中是合法的敘述:

switch (TRUE) {
    case ($v_BAL <= 0):
        echo "$v_BAL less then 0";
        break;
    case ($v_BAL <= 10 AND $v_BAL >= 1):
        echo "$v_BAL less then 10 and more then 1";
        break;
    default:
        echo "$v_BAL more then 10";
}

在條件分支中如果狀況只有 True 和 False 兩種,可以把 if 敘述縮寫成 (?:) 格式:

$flag = (substr($ip,0,7)=='172.16.' ? "inside" : "outside");

定量迴圈、不定量迴圈、無窮迴圈與巢狀迴圈

PHP 提供四種迴圈敘述 for、while、do...while、foreach,可以適用於各種不同用途。其中最為人熟知的 for 迴圈用來處理定量迴圈,所謂定量迴圈意指在寫程式之初已經知道迴圈應該要跑幾次,而這個次數可以使用常數或透過變數取得,舉後者為例:

<?
$max=count($scores);
for ($i=$max;$i>=0;$i--) {
    $mysco=$scores[$i];
    if ($mysco<60) $mysco='<font color="red">'.$mysco.'</font>';
}
?>

上面程式中 $scores 是一個陣列變數,習慣上在使用陣列時,都會在名稱後面加上 s,也就是英文中的複數。count() 函式用來計算陣列的元素個數,也就是陣列大小。由於迴圈次數可以事先透過函式取得,因此直接使用 for 敘述即可。 當迴圈變數是從數字大到數字小變化時,必須加上 $i-- 敘述來減量,如同上面的範例。for 迴圈在進行中,有時遇到某些情況必須做例外處理,可以使用 break 敘述來中斷迴圈。

如果資料是來自外部檔案或後端資料庫,無法在取得資料之前計算出資料量,這種情況下必須使用不定量迴圈,所謂不定量迴圈意指在寫程式之初無法得知迴圈應該要跑幾次,因此必須透過條件判斷來決定是否結束迴圈,這部分的敘述語法種類很多,也各有不同用途。其中最為人熟知的是 while (do...while) 迴圈:

<table>
<?
$conn=mysql_connect($db_ip,$db_user,$db_pw);
mysql_select_db("artsnsc",$conn);
$rs_query=mysql_query($sql,$conn);
$myrs=mysql_fetch_array($rs_query);
while (!($myrs==0)) {
?>
  <tr>
      <td><?=$myrs[0]?></td>
      <td><?=$myrs[1]?></td>
      <td><?=$myrs[2]?></td>
  </tr>
<?
    $myrs=mysql_fetch_array($rs_query); 
}
?>

上面範例中用到了很多存取資料庫的語法,在後面章節中有詳細的介紹,現在只要注意 while 迴圈的用法。無窮迴圈將反覆執行同一工作,一直到 符合某特定條件時才會中斷執行,通常運用在系統程式或遊戲程式的主迴路中。在網頁應用程式的開發上,無窮迴圈的需求不大,語法如下:

while (TRUE) {
   ......A.....
   if (中斷條件) break;
   ......B.....
}

使用無窮迴圈需要有離開迴圈的方法,這種情形下可以使用 break 敘述中斷迴圈,以上範例中的 B 區塊程式碼將不會被執行。如果把 break 敘述改成 continue 敘述,則當範例中的條件成立時,會跳回迴圈的開頭,重新再執行下一回合的運算,同樣的在該回合中 B 區塊程式碼將不會被執行。

最後一種迴圈是 for 迴圈的變形,稱為 foreach 迴圈,在前面的範例中,我們利用 count() 函式來偵測陣列大小,根據陣列大小來進行迴圈,但當陣列元素個數會隨著程式處理而變化時,很適合使用 foreach 迴圈。

foreach ($_POST as $data) {
    echo
"表單元件的值是 $data <br>";
}

foreach ($_POST as $key => $data) {
    echo
"表單元件 $key 的值是 $data <br>";
}

以上所有迴圈都支援巢狀寫法,底下的範例將使用巢狀迴圈輸出九九乘法表:

<table>
<? for($i=1;i<=9;i++) {?>
    <tr>
        <? for ($j=1;$j<=9;$j++) {?>
        <td><?=i?> x <?=j ?> = <?=i*j?></td>
        <? }?>
    </tr>
<? }?>
</table>

導入外部程式碼

為了讓網頁應用程式的設計比較模組化,在 PHP 上也流行使用物件導向(OOP)或 MVC 兩種方法來設計程式,這兩種方法也可以混用。在物件導向設計中,網頁應用程式將可分解為類別定義與主程序,分別儲存為 *.inc.php 和 *.php 兩種不同附加檔名,這樣做是為了方便讓設計好的物件模型可以反覆被其它程式導入(include)應用。

而在 MVC 開發模式上,Module 為函式庫,Control 為主程序,而 View 是網頁樣板;Module 將被導入到 Control 中以便進行運算,同時使用 View 來生成網頁。在上述兩種開發模式中,都需要使用 include 和 require 來導入外部程式碼:

include 會將外部程式碼完整讀入記憶體中與主程式合併運算。
require 會檢查該程式是否已經被導入,若是則忽略不處理。

錯誤處理

執行 PHP 程式時,有時會因為某些突發狀況,造成程式無法正確執行,尤其是進行檔案IO處理、呼叫外部程序或是透過網路連接資料庫時,意外的發生是無法預期的。這時候,可以利用控制結構 try 以及 catch 來預防錯誤發生:

try {
    ………………資料庫連線…………………
} catch (Exception $e) {
    die( 'Error:'.$e->getMessage() );
}

當執行時期發生錯誤時,會執行 catch 敘述中的程式碼,這時可以呼叫 Exception 物件中的 getMessage() 方法(使用物件導向的成員運算子 -> 來呼叫)將錯誤訊息印出來。另外還可以利用 throw 敘述主動觸發錯誤事件,以便讓 catch 區塊中預設的程式發揮作用:

Throw new Exception(‘帳號密碼錯誤!資料庫連線無法建立!’);

 

PHP 的自訂函式

參數預設值

底下是一個自訂函式的例子,這個函式可根據參數值輸出指定的空白行:

function newline($num) {
    for ($i=0;$i<$num;++$i)
        echo "<br>\n";
}

而參數可以指定預設值,有指定預設值的參數在呼叫函式時可以省略,例如:

function newline($num=1) {
    for ($i=0;$i<$num;++$i) echo "<br>\n";
}

產生結果如下:

newline(2); // 輸出兩個空白行
newline(); // 輸出一個空白行

巢狀函式

巢狀函式在 PHP 中雖然是合法的,但應該盡量避免使用,底下是一個計算球體體積的函式:

function vol($r) {
    function cube($num) {
        return $num * $num * $num;
    }
    return M_PI * cube($r);
}

$radium = 2;
echo vol($radium); // 25.13272
echo cube(4); // 64

遞迴函式

遞迴函式可以有效減少程式碼,讓複雜的程式變成短短幾行(付出執行效率和記憶體空間作代價):

function qsort($v) {
    $n=count($v);
    if ($n>1) {
        $pole=$v[1];
        for ($k=2;$k<=$n;$k++) {
            if ($v[$k]<pole)
                $u[]=$v[$k];
            else
                $w[]=$v[$k];
        }
        return array( qsort($u) , $pole , qsort($w) );
    } else
        return $v;
}

可變函式

PHP 允許使用變數來呼叫函式,在特定的場合裡,這個功能很好用,例如:

Function load_nn() { ……略……}
Function load_ie() { ……略……}
Function load_other() { ……略……}

if ($browser_type=="NN") $loder="load_nn";
elseif ($browser_type=="IE") $loder="load_ie";
else $loder="load_other";

$loader($URI);

$browser_type 用來儲存 $_SERVER['HTTP_USER_AGENT'] 字串的剖析結果,程式會根據不同瀏覽器,呼叫適當的函式來輸出不同版型的網頁!

 

網址列及表單傳值

當參觀者連上 Apache 伺服器時,除了傳輸所要求網頁的網址之外,也會附帶傳輸兩種參數,一種稱為 URI ,另一種稱為 cookie。伺服器必須去剖析這些參數,才能夠得知使用者的要求與命令,而去進行相對應的處理,並將處理結果以網頁型式傳回給參觀者。

URI 傳值主要用途是協調瀏覽器與伺服器的設定,例如:語系、傳值編碼方式、版本功能訊息、帳號認證、IP位址......等等。由於這些資訊可以決定伺服器將傳遞何種 HTML 內容給瀏覽器,瀏覽器將以何種方式顯示伺服器送來的 HTML ,因此 URI 一般也被稱為 HTML 表頭。

其實以程式開發的角度來看,我們比較關心的是,如何將程式執行時所需要的參數傳給伺服器,在這方面主要有兩種做法,一種稱為 QueryString 傳值,另一種方法是透過 Form 表單傳值。前者的運作方式是,瀏覽器將參數(須先以 URL-encoding 方式編碼)串接在 URL 網址後面,中間用 ? 隔開,然後要求伺服器以 GET 方式處理 URI ,伺服器接獲要求會從 URI 萃取 URL 網址 ? 以後的字串,將參數放置在 $_GET,提供給 PHP 程式。由於 URI 欄位有大小限制,無法用來傳送大量資料,因此使用 QuerryString 傳值時,資料量不可以大於 2MB。

Form 表單傳值則視場合而有不同,如果使用 GET 方式來傳遞,則做法與 QueryString 完全相同,另一種方式是使用 POST 方式來傳遞。後者是使用資料串流的技術進行傳送,所以能夠擺脫 URI 的欄位大小限制,進而連大型檔案都可以透過表單上傳。 表單資料會儲存在 $_POST 陣列中,上傳的檔案則儲存在 $_FILES 陣列(實體檔案位於 /tmp 資料夾),等候 PHP 程式取出作進一步的處理。

前面在討論條件判斷時,曾經寫過一個檢查體重的程式片段,我們現在就把它完成,順便利用這個範例來比較網址列傳值與表單傳值的異同,先來看 check_weight.php 程式碼:

<?
$myheight=$_GET['height'];
$myweight=$_GET['weight'];

if ($myweight>($myheight-100)*0.8) {
    $mymsg="體重過重,該減肥了";
} elseif ($myweight<($myheight-100)*0.8) {
    $mymsg="體重過輕,多吃點";
} else {
    $mymsg="標準身材";
}
?><H1><?=$mymsg?></H1>

請打開瀏覽器輸入網址,檔名後面接 ?height=170&weight=70,按 Enter 查看結果。同樣的程式如果要改用表單讓參觀者輸入身高體重,請改寫前二行為:

$myheight=$_POST['height'];
$myweight=$_POST['weight'];

表單的製作不在這裡討論,請注意表單標籤要加入 action 屬性,這是用來定義表單資料將傳送到哪一支程式,例如:

<form action="check_weight.php">
身高:<input type="text" name="height"><br>
體重:<input type="text" name="weight"><br>
<input type="submit" value="送出">
</form>

比較兩種方式,我們發現:

  1. 網址列傳值使用簡便,而表單傳值還得額外製作一個網頁,比較麻煩。
  2. 網址列傳值會把變數顯示在網址列,容易被別的站台依樣畫葫蘆加以套用,表單傳值相對較為隱密。

如果要同時允許兩種傳值方式,在讀取 Request 物件時可以省略陣列名稱,把程式改寫為:

$myheight=$_REQUEST['height'];
$myweight=$_REQUEST['weight'];

表單欄位檢查

在上述範例中,參觀者從表單輸入的內容,在程式進行運算之間應該要先檢查,以避免程式發生錯誤。檢查的方式有兩種,第一種是在 PHP 程式中撰寫,例如:

$myheight=(int) $_POST['height'];
$myweight=(int) $_POST['weight'];
if ($myheight<=0 && $myweight<=0) {
    echo "身高和體重輸入不正確!<br>請回上一頁重新輸入";
    exit;
}

在伺服端檢查萬一有問題時,還是要回到用戶端去修正,這種作法會影響到伺服器的效能,應該盡量避免使用。第二個方法是使用 JavaScript 在用戶端送出表單資料之前先檢查。表單欄位檢查的功能經常要使用,所以寫成獨立函式庫:validator.js

//validator.js 表單欄位檢查程式
//本程式遵循 GPL 版權規定,您必須完整保留作者資訊,否則即侵犯作者人格權
//作者:李忠憲 sean@tp.edu.tw
//正規表示式樣式定義
var EMAIL_FORMAT=/^[a-z0-9]+([-+_\.]?[a-z0-9]+)*@[a-z0-9]+([-_\.]?[a-z0-9]+)*\.[a-z]{2,4}/;
var HTTP_FORMAT=/^http:\/\/[a-z0-9]+([-_\.]?[a-z0-9]+)*\.[a-z]{2,4}/;
var IP_CIDR_FORMAT=/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\/(\d{1,2})$/;
var IP_FORMAT=/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
var INT_FORMAT=/^([1-9]\d*)|0$/;
var DATE_FORMAT=/^(\d{4})[-\/\. ]?(\d{1,2})[-\/\. ]?(\d{1,2})$/;
var TEL_FORMAT=/^(\d{7,8})$/;
var TEL_CIDR_FORMAT=/^0(\d?)[-\)_ ]?(\d{7,8})$/;
var MOBILE_FORMAT=/^09(\d{2})[-\)_ ]?(\d{6})$/;
var ACCOUNT_FORMAT=/^[a-zA-Z]\S*$/;
var ID_FORMAT=/^[A-H|J-N|P-Z|a-h|j-n|p-z]\d{9}$/;
//檢查欄位是否為空白或空值
function Blank(field) {
    var blankness=/^\s*$/;
    return field.value=='' || blankness.test(field.value);
}
//檢查欄位是否為零值
function Zero(field) {
    return field.value=="0";
}
//檢查欄位是否為空白或空值或零值
function Empty(field) {
    return Blank(field) || Zero(field);
}
//檢查欄位輸入是否為數字
function Numeric(field) {
    return Blank(field) || isNaN(field.value);
}
//檢查核取框或選擇鈕是否被勾選
function isChecked(field) {
    if (field.length) {
        for (var i = 0; i < field.length; i++) {
            var e = field[i];
            if (e.checked) return true;
        }
    } else {
        return field.checked;
    }
    return false;
}
//檢查輸入字串的長度是否符合限制
function LengthRange(field,min,max) {
    if (Blank(field)) return false;
    return field.value.length >= min && field.value.length <= max;
}
//檢查輸入數字的大小是否符合限制
function ValueRange(field,min,max) {
    if (Blank(field)) return false;
    if (!isNaN(field.value)) return false;
    return field.value >= min && field.value <= max;
}
//檢查字串樣式是否相符
function Match(field,regex) {
    return regex.test(field.value);
}
//檢查字串樣式是否相異
function NotMatch(field,regex) {
    return !regex.test(field.value);
}
//檢查字串樣式是否為電子郵件
function isEmail(field) {
    if (Blank(field)) return false;
    return Match(field,EMAIL_FORMAT);
}
//檢查字串樣式是否為網址
function isHTTP(field) {
    if (Blank(field)) return false;
    return Match(field,HTTP_FORMAT);
}
//檢查字串樣式是否為 IP
function isIP(field) {
    if (Blank(field)) return false;
    return Match(field,IP_FORMAT);
}
//檢查字串樣式是否為日期格式
function isDate(field) {
    if (Blank(field)) return false;
    return Match(field,DATE_FORMAT);
}
//檢查字串樣式是否為電話號碼
function isTel(field) {
    if (Blank(field)) return false;
    return Match(field,TEL_FORMAT) || Match(field,TEL_CIDR_FORMAT) || Match(field,MOBILE_FORMAT);
}
//檢查字串樣式是否為合法帳號格式
function isAccount(field) {
    if (Blank(field)) return false;
    return Match(field,ACCOUNT_FORMAT);
}
//檢查字串樣式是否為身分證字號
var local=new Array(34)
local[10]='A'
local[11]='B'
local[12]='C'
local[13]='D'
local[14]='E'
local[15]='F'
local[16]='G'
local[17]='H'
local[18]='J'
local[19]='K'
local[20]='L'
local[21]='M'
local[22]='N'
local[23]='P'
local[24]='Q'
local[25]='R'
local[26]='S'
local[27]='T'
local[28]='U'
local[29]='V'
local[32]='W'
local[30]='X'
local[31]='Y'
local[33]='Z'
function isID(field) {
if (Blank(field)) return false;
if (NotMatch(field,ID_FORMAT)) return false;
var se=new Array(10)
var we=0
var checkcode=0
for(i=10;i<=33;i++){
if(local[i]==field.value.substring(0,1).toUpperCase()){
se[0]=parseInt((i+'0').substring(0,1))
se[1]=parseInt((i+'0').substring(1,2))
break
}
}
for(i=1;i<=9;i++){
se[i+1]=parseInt(field.value.substring(i,i+1))
}
for(i=0;i<=10;i++){
if(i==0)
we=we+se[i]
else
we=we+(se[i]*(10-i))
}
checkcode=((10-(we%10))+'0').substring(0,1)
return checkcode==parseInt(field.value.substring(9,10))
}

接著把上面的程式導入到表單網頁中,並加入欄位檢查的 JavaScript 程式碼:

<HEAD>
<script language="JavaScript" src="/include/validator.js"></script>
<SCRIPT LANGUAGE="JavaScript">
function Check(thisform)
{
    thisform=document.AFORM;
    if (Empty(thisform.height)) {
        alert("身高必須輸入");
        thisform.height.focus();
        return;
    }
    if (Empty(thisform.weight)) {
        alert("體重必須輸入");
        thisform.weight.focus();
        return;
    }
    if (!Numeric(thisform.height)) {
        alert("身高必須是數字");
        thisform.height.focus();
        return;
    }
    if (!Numeric(thisform.weight)) {
        alert("體重必須是數字");
        thisform.weight.focus();
        return;
    }
    if (!ValueRange(thisform.height,100,250)) {
        alert("身高必須在 100 到 250 之間");
        thisform.height.focus();
        return;
    }
    if (!ValueRange(thisform.weight,20,200)) {
        alert("體重必須在 20 到 200 之間");
        thisform.weight.focus();
        return;
    }
}
</script>
</HEAD>
<BODY>
<form name="AFORM" action="check_weight.php">
身高:<input type="text" name="height"><br>
體重:<input type="text" name="weight"><br>
<input type="button" value="送出" onclick="Check()">
</form>
</BODY>

作業1:延續前例,當同時使用兩種傳值方式時,結果以何者為主?

<form action="check_weight.asp?height=160&weight=40">
身高:<input type="text" name="height">
體重:<input type="text" name="weight">
<input type="submit" value="送出">
</form>

檔案上傳

使用網頁進行檔案上傳工作,首先必須先製作上傳表單,與一般表單不同的是,上傳表單必需使用 multipart/form-data 來編碼,這種編碼格式當初是設計給 sendmail 伺服器郵寄附加檔案用的,它就跟 MIME 格式一樣廣為流傳,被後來的各種伺服器繼續引用。表單設計如下:

<form method="POST" action="upload.php" enctype="multipart/form-data">
作者:<input type="text" name="author"><br>
檔案:<input type="file" name="upfile"><br>
<input type="submit" value="開始上傳">
</form>

上面表單的作者欄位,是為了避免上傳檔案因為檔名相同而互相覆蓋,我們希望在檔名前面,加上作者名字,以方便辨別檔案避免不同作者的檔案互相覆蓋。upload.php 的程式範例如下:

<?
$save_path='/var/www/html/files/';
$author=$_POST['author'];
$myfile=$_FILES['upfile'];
if ($myfile['size']>0) {
    $filename=basename($myfile['name']);
    $succ=move_uploaded_file($myfile['tmp_name'],
                             $save_path.$author.'_'.$filename);
    if ($succ) {
?>
<p>檔案上傳成功!<br>
檔案名稱:<?=$author.'_'.$filename?><br>
檔案大小:<?=$myfile['size']?>位元組(Bytes)</p>
<?     
    }
}
?>
<a href=javascript:history.go(-1)>回上頁!</a>

從表單上傳的非檔案資料會擺放在 $_POST 陣列中,在上例中的作者姓名將透過此方式取得。上傳檔案資料則會擺放在 $_FILES 陣列中,使用 $_FILES[表單欄位名稱]['size'] 語法來取得檔案的大小,並從 $_FILES[表單欄位名稱]['name'] 取得上傳檔案的原始路徑與檔名,利用 basename() 函式將路徑去除,得到檔名。上傳成功的檔案會暫時存放在 /tmp 資料夾中,可以使用 $_FILES[表單欄位名稱]['tmp_name'] 語法取得暫存檔的路徑和檔名,接著利用 move_uploaded_file() 函式,將檔案搬移到預定的目錄下,也可以使用 copy() 函式進行檔案的備份複製。這兩個函式用法差不多,檔案處理完畢都會有傳回值,判斷傳回值的真假就能得知檔案處理是否成功。在上例中,傳輸成功會顯示檔案相關訊息,您可以自行加入失敗時的處理程序,例如:顯示警告訊息。

有一個地方要特別注意,就是要把檔案存入磁碟必須要有適當權限,由於 Apache 是使用 apache 帳號來執行程式,因此程式中用來存放檔案的 $save_path 資料夾,必須設定成 apache 帳號可以讀寫才行。

作業2:請改寫上面程式,先檢查是否有同檔名檔案已經存在,若是則顯示提示訊息(請先修改檔名!)若否才搬移檔案!

PHP 檔案上傳設定

檔案上傳如果失敗,請檢查是否為 PHP 的限制造成的,打開 /etc/php.ini,修改底下的相關設定:

; Whether to allow HTTP file uploads.
file_uploads = On

; Temporary directory for HTTP uploaded files (will use system default if not
; specified).
;upload_tmp_dir =

; Maximum allowed size for uploaded files.
upload_max_filesize = 32M

 

活用 Header

header() 函式用來輸出 HTTP 表頭(URI),原本 HTTP 表頭是由 Apache 自動產生的,當使用header() 函式時,將會取代 Apache 表頭中的參數,並在稍後網頁輸出時一併傳送給用戶端。利用表頭輸出,除了可以傳送 HTTP 錯誤訊息以外,還可以做到以下的功能:

  1. 網頁轉向

  2. 設定 Cookie

  3. 利用程式將網頁輸出成各種格式的檔案,提供下載(線上產生 word、excel、pdf、圖片或 flash)

  4. 控制快取

  5. 身分驗證

網頁轉向

傳統的網頁轉向是使用 JavaScript 來設計,例如:

<script>
alert("歡迎<?=$user?>光臨!")
location="<?=$next_page?>"
</script>

當我們利用網頁轉向根據日期來改變首頁時,使用 JavaScript 程式碼將會很複雜不夠簡潔,這時候可以利用header() 函式來處理。我們可以在不同節日顯示不同風格的網頁,讓網頁依照排程自動切換,而不必人工管理,底下程式將使用日期函式取得系統日期,根據系統日期判斷要顯示哪一個首頁:

index.php

<?
$mydate=getdate();
$myday=$mydate['mday'];
switch ($mydate['mon']) {
    case 1:
        if ($myday<=15) $homepage='過新年.htm';
        break;
    case 6:
        if ($myday>=10) $homepage='畢業了.htm';
        break;
    case 7:
        $homepage='放暑假.htm';
        break;
    case 10:
        if ($myday<=20) $homepage='國慶日.htm';
        break;
    case 11:
        if ($myday>=10 && $myday<=25) $homepage='校慶.htm';
        break;
    case 12:
        if ($myday>=15) $homepage='聖誕節.htm';
        break;
}
header("Location: $homepage");
?>

作業3:有一個網頁 games.htm 不希望學生於上課時間連結,請設計程式當參觀者於早上 8:00 到下午 4:00 這段時間連線時,自動轉向到 warring.htm。

設定 Cookie

當我們想要提供參觀者,可以自訂個別化外觀的功能時,由於必須將設定資訊保留超過一天以上,必須使用 Cookie 來設計。利用 Cookie 可以允許參觀者自訂網站的版面,以便下次連上網站時顯示自己感興趣的資訊 ,Cookie 資料的設定可以透過 header() 函式來進行:

<?
function set_cookie($Name, $Value='', $MaxAge=0, $Path='', $Domain='', $Secure=false, $HTTPOnly=false) {
  header('Set-Cookie:' . rawurlencode($Name) . '=' . rawurlencode($Value)
                         . (empty($MaxAge) ? '' : '; Max-Age=' . $MaxAge)
                         . (empty($Path)   ? '' : '; path='. $Path)
                         . (empty($Domain) ? '' : '; domain='. $Domain)
                         . (!$Secure       ? '' : '; secure')
                        
. (!$HTTPOnly     ? '' : '; HttpOnly'), false);
}

set_cookie("TestCookie", $value);  /* 未指定時間,所以 Cookie 將在斷線後直接刪除 */
set_cookie("TestCookie", $value, 3600, "", ".example.com", 1); /* 一小時後過期 */
?>

檔案下載

使用 header() 函式來改變 HTTP 表頭,就能夠把程式的 URL 偽裝成各種不同格式的檔案,參觀者就能很方便的取得程式自動產生的檔案,例如:

<?
.................
(略)...............
header
('Content-type: application/vnd.ms-excel');
header('Content-Disposition: attachment; filename="'.$class_name.'通訊錄.xls"');
?>
<table border="1">
<tr>
    <td x:str="座號">座號</td>
    <td x:str="姓名">姓名</td>
    <td x:str="
聯絡電話">聯絡電
話</td>
</tr>
<? while (!($rs==0)) {?>
<tr>
    <td x:num="<?=$rs['seat']?>"><?=$rs['seat'] ?></td>
    <td x:str="<?=$rs['name']?>"><?=$rs['name'] ?></td>
    <td x:str="<?=$rs['tel']?>"><?=$rs['tel']?></td>
</tr>
<?     $rs=mysql_fetch_array($rs_query);
}?>
</table>

上面程式將會讀取班級資料表,並將座號、姓名、聯絡電話輸出成 Excel 格式,如果要輸出成其他格式,你必須仔細研讀該格式的詳細結構,了解如何製作相容於該格式的內容,這部份屬於進階程式設計的範圍不再這裡討論。常見的輸出格式還有:

常用 MIME 格式
MS Word(*.doc) application/msword
PDF(*.pdf) application/pdf
WinZIP(*.zip) application/x-zip-compressed
JavaScript(*.js) application/x-javascript
CSS(*.css) text/css
Flash(*.swf) application/x-shockwave-flash

如果檔案已經事先存在,不需要使用程式來製作,也可以利用這個方式在提供檔案的同時,進行計數或者額外的存取控管,例如:

<?
$conn=mysql_connect($db_ip,$db_user,$db_pw);
mysql_select_db("download",$conn);
if ($_SESSION['member']) { //判斷是否為會員
    $sql="UPDATE count=count+1 WHERE id=".$_GET['id']; //將下載計數器加 1
    $rs_query=mysql_query($sql,$conn);

    $sql="SELECT * WHERE id=".$_GET['id'];
    $rs_query=mysql_query($sql,$conn);
    $rs=mysql_fetch_array($rs_query);
    header('Content-type: application/pdf');
   
header('Content-Disposition: attachment; filename="'.$rs['filename'].'"');
    readfile(
$rs['filename']
);
}
?>

上面程式中的 readfile() 函式將會讀取指定的檔案內容,並將內容以二進位資料傳輸給瀏覽器。

控制快取

如果網頁只是因應某個活動而暫時存在,希望在活動過後,參觀者無法再看到舊的網頁內容,可以利用 HTTP 表頭中的 Cache-Control 和 Expires 參數來控制,例如:

<?php
header
("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
header("Expires: Mon, 26 Jul 2007 05:00:00 GMT"); // Date in the past
?>

控制快取可以強迫 proxy 伺服器不要儲存該網頁,因此該網頁將不會被 Google 或其他搜尋引擎收錄為庫存頁面。

身份驗證

利用 HTTP 表頭中的 WWW-Authenticate 參數,可以要求瀏覽器進行身分驗證,瀏覽器將顯示登入視窗要求參觀者登入。當然您也可以自己設計登入表單,但使用這個方式比較簡單易用:

<?php
session_start();
$topage=$_GET["topage"];

function LoginOK() {
    $user = $_SERVER['PHP_AUTH_USER'];
    $pass = $_SERVER['PHP_AUTH_PW'];
    if (!empty($user)) {
        if ($user == "admin" && $pass == "mypasswd") return true;
    }
    return false;
}

if (LoginOK()) {
    $user = $_SERVER['PHP_AUTH_USER'];
    $_SESSION['member']=TRUE;
    header("Location:index.php?dir=$topage");
} else { // 告知使用者必須登入,或如果他取消的話就輸出錯誤訊息
    header('WWW-Authenticate: Basic realm = "請輸入名稱及密碼"');
    header('HTTP/1.0 401 Unauthorized');
    echo "您必須登入以使用受保護的的功能!";
    exit;
}
?>

程式要求瀏覽器進行 HTTP 登入,Basic 是指純文字密碼傳輸,如果想要使用加密,可以改成 Digest。

Session 的應用

Session 的功能改良自 Cookie,與 Cookie 一樣用來紀錄用戶端與伺服端的連線資訊,因此很適合拿來作為儲存身分資訊及判斷使用者權限的用途。兩者的差異整理如下表:

  Session Cookie
儲存方式 變數儲存在伺服器的主記憶體中,系統會自動產生 Session ID 作為索引值,該索引值以 Cookie 或 URI 形式儲存於用戶端 儲存於用戶端的硬碟中
存活時間 到斷線為止(關閉瀏覽器),或連線超過 20 分鐘未更新 時間自訂,從斷線後清除,一直到保存一年以上都可以
安全性 Session ID 以 MD5 編密,安全性高 純文字資料,安全性差(可用程式技巧改善)
頻寬節流 僅傳送 Session ID,省頻寬 傳送所有變數,耗頻寬
系統資源 佔用主系統記憶體,連線數過多時容易造成伺服器當機 只耗用用戶端的記憶體,影響不大

底下舉實例來說明 Session 物件如何應用在身分驗證與權限管理上:假設現有會員 mary 和 john 兩人,密碼分別為 123 和 abc,會員 mary 為管理員而 john 為一般會員。先來製作登入表單 logon.htm:

<form method="POST" action="auth.php">
帳號:<input type="text" name="username"><br>
密碼:<input type="password" name="password"><br>
<input type="submit" value="登入">
</form>

身分驗證程式 auth.php 驗證完身分之後,將驗證結果和身份資訊儲存在 Session 物件中備用:

<?
$myuser=$_POST["username"];
$mypass=$_POST["password"];

if ($myuser=="mary" && $mypass=="123") {
    $_SESSION["user"] = $myuser;
    $_SESSION["pass"] = $mypass;
    $_SESSION["admin"] = true;
} elseif ($myuser=="john" && $mypass=="abc") {
    $_SESSION["user"] = $myuser;
    $_SESSION["pass"] = $mypass;
    $_SESSION["admin"] = false;
} else {
    unset($_SESSION["user"]);
}

if (isset($_SESSION["user"])) {?>
歡迎<? if ($_SESSION["admin"]) {?>管理員<? } else {?>會員<? } ?>
<?=$_SESSION["user"]?>光臨本站!
<? }?>

假設現有留言版程式一組,其中 manager.php 用來給管理員刪除或回應留言,write.php 用來給一般會員留言或回應留言。在 manager.php 程式中我們可以利用 $_SESSION["admin"] 來判斷權限,如果參觀者尚未登入則將它導向到登入網頁:

<?
if (!isset($_SESSION["user"])) {
    header("Location: logon.htm");
    exit;
}
if (!$_SESSION["admin"]) {
?>
您不是管理員,沒有足夠權限使用本程式!!
<?
    exit;
}
?>
...................(略)...................

isset() 函式用來檢查某個變數是否存在,上面範例先檢查使用者是否已經登入,如果已經登入應該會有 $_SESSION['user'] 這個變數,如果沒有,就代表使用者尚未登入。如果使用者已經登入,那麼就判斷其身份是否為管理員,如果不是則輸出警告訊息。

現在請假設您自己是忘記要登入的管理員,您直接連到 manager.php,結果程式將您導向到登入畫面,您登入成功後看到了歡迎訊息,然後呢?然後居然還得自己再連一次 manager.php!這樣的設計當然還有待改善,如果要讓系統自動跑回 manager.php,該如何設計呢?當然不能直接轉向到 manager.php,否則每個人登入完,都會跑來這裡!

答案一樣是利用 Session 來解決,改寫身分驗證程式 auth.php,請在最後一行加入底下的程式碼:

<?
if ($_SESSION['topage']) {
    header('Location: '.$_SESSION["topage"]);
}
?>

上面程式將檢查是否有 $_SESSION['topage'] 變數,如果有則自動轉向到指定的網頁,從此以後,只要是想登入完自動轉向的程式,事先設定好轉向目標就可以了。同樣舉 manager.php 為例,請在程式開頭加入:

$_SESSION["topage"]="manager.php";

作業4:請完成 write.asp 程式。

登出與重新登入

在前一小節我們已經完成了登入驗證程式,現在應該來設計登出程式。登出程式的工作,就是負責移除 Session 裡面的相關身分資訊,可以使用 unset() 函式來處理:

<?
unset($_SESSION["user"]);
unset($_SESSION["pass"]);
unset($_SESSION["admin"]);
?>

另一個更簡便的解決方案是使用 session_destroy() 函式:

<? session_destroy();?>

使用這個方法將會清除所有儲存在 Session 中的變數,因此如果站台上還有其它程式也利用 Session 物件保存自己的變數,貿然使用 session_destroy() 將造成該程式無法正確執行。現在大部分的站台都是下載安裝現成套件,套件裡面到底有哪些 Session 變數需要保留身為站長的你恐怕也搞不清楚,所以還是謹慎一點好!

重新登入程式,其實就是登出之後,重新導向到登入網頁而已。

作業5:延續前面的作業,請設計重新登入程式。

 

處理電子郵件

越來越多的 WEB 應用程式要利用到電子郵件寄發,例如:電子賀卡、電子報、申請會員、網頁郵寄...等。這時候就需要使用 mail() 函式:

<?php
$body='親愛的訂戶:<br>XX國小網站新增【<a href="http://www.xxps.tp.edu.tw/poto">影像館</a>】單元,歡迎您前往參觀!';
$headers = "From: webmaster@mail.spps.tp.edu.tw \r\n";
$headers .= "Content-Type: text/html; charset=big5 ";
$headers .= "MIME-Version: 1.0 ";
mail("sean@tp.edu.tw", "XX國小網站更新通知", $body, $headers);
?>

上面範例所產生的郵件是 HTML 格式,因此使用 <br> 來換行,如果是寄送純文字郵件,則必須改成 \r\n 脫逸字元來換行。

作業6:請繼續發展上一個作業,將郵寄留言訊息的功能加入到 write.php 程式中,假設訂戶有下列三人:mary@ms8.hinet.net、john@tp.edu.tw、jack@hotmail.com(提示:使用陣列和迴圈來簡化程式)

作業7:請設計一個表單讓使用者可以自行輸入:寄信人、收信人、郵件主旨、郵件內文,然後修改上面的寄信程式,將此郵件寄送出去!

寄送附件的處理

首先附件檔案必須先利用表單上傳到伺服器上,在前面已經練習過檔案上傳,不再贅述。附件檔案在送出之前必須先編碼成 base64 格式才能寄送,方法如下:

$filename="/var/www/html/attach/demo.zip";
$handle=fopen($filename,"rb");
$content=fread($handle,filesize($filename));
$content_b64=base64_encode($content);

帶有附件的郵件必須修改郵件標頭及郵件內文,以下程式碼請取代前一小節範例的最後ㄧ行(有mail()函式那一行):

$boundary=md5(time());
$headers .= 'Content-Type: multipart/related; boundary="'.$boundary.'"'."\r\n";
$msg = "Content-Type: multipart/alternative\r\n";
$msg .= "--".$boundary."\r\n";
$msg .= "Content-Type: text/html; charset=big5\r\n";
$msg .= "Content-Transfer-Encoding: 8bit\r\n";
$msg .= $body."\r\n\r\n";

$filename="/var/www/html/attach/demo.zip";
$handle=fopen($filename,"rb");
$content=fread($handle,filesize($filename));
$content_b64=base64_encode($content);
$msg .= "--".$boundary."\r\n";
$msg .= "Content-Type: ".mime_content-type($filename).";";
$msg .= ' name="'.basename($filename).'"'."\r\n";
$msg .= $content_b64."\r\n\r\n";

$msg .= "--".$boundary."--";
mail("sean@tp.edu.tw", "XX國小網站更新通知", $msg, $headers);

 

檔案管理員

PHP 內建完整的檔案系統函式,但是這些函式是依據 Linux 檔案系統設計的,如果您的程式是要設計給 Win32 平台使用,有部分函式會不起作用,常用的函式表列如下:

pathinfo()
basename()
dirname()
realpath()
取得路徑完整資訊
取得路徑的目錄名稱
取得不包含路徑的檔案名稱
將網頁相對路徑轉換為磁碟絶對路徑
file_exists()
is_dir()
is_file()
is_link()
檢查檔案是否存在
辨別檔案名稱是否為目錄
辨別檔案名稱是否為檔案
辨別檔案名稱是否為捷徑
fileatime()
filectime()
filegroup()
fileowner()
fileperms()
filesize()
flock()
取得檔案建立日期
取得檔案修改日期
取得檔案擁有群組
取得檔案擁有者
取得檔案權限設定
取得檔案大小
鎖定檔案
chmod()
chown()
mkdir()
rmdir()
unlink()
copy()
move_uploaded_file()
變更檔案權限
變更檔案擁有者
建立目錄
刪除目錄
刪除檔案
複製檔案
移動上傳檔案
opendir()
closedir()
readdir()
readfile()
fopen()
fread()
fwrite()
開啟目錄,並取得 dir handle
關閉目錄
讀取 dir handle 的內容
讀取並將檔案內容輸出成網頁
開啟檔案,並取得 file handle
讀取 file handle 的內容
將資料寫入 file handle

我們可以利用上面介紹的函式來設計檔案管理員程式,它能讀取指定目錄中的資料夾及檔案,並以網頁格式顯示,提供切換目錄與檔案下載功能。先來設計讀取目錄內容的函式:

function get_dir($base_dir) {
    global $folders;
    global $files;
    if (is_dir($base_dir)) {
        $dh = opendir($base_dir);
        while ($dir = readdir($dh)) {
            if (is_dir($base_dir ."/". $dir) && $dir !== '.' && $dir !== '..') {
                $folders[]=$dir;
            } elseif (is_file($base_dir ."/". $dir) && $dir !== '.' && $dir !== '..') {
                $files[]=$dir;
            }
        }
        closedir($dh);
    }
}

利用下面的程式碼將目前所在目錄下的所有子目錄,以表格方式呈現。程式利用 realplath() 函式將網頁相對目錄轉換成磁碟絕對路徑,以便從硬碟讀取完整目錄清單。接著把 子目錄名稱逐一擺入表格中,程式利用變數 $i 累計目前的子目錄數量,運算子 % 用來求取餘數,當 $i 等於 5 的倍數時(除以 5 餘數為 0 時),就將表格換行,也就是說每行最多顯示 5 個子目錄。

<?
$mydir=$_GET["dir"];
get_dir(realpath(substr($mydir,1)));
?>
<table border=0 align="center" cellpadding="0" cellspacing="10">
<tr>
<?
$i=0;
foreach ($folders as $dir_name) {
?>
    <td>
    <a href="index.php?dir=<?=$mydir."/".$dir_name?>"><?=$dir_name?></a>
    </td>
<?
    $i++;
    if ($i % 5 == 0) {?>
        </tr><tr>
<?  }
}
?>
</tr>
</table>

作業8:請參考上面的程式將顯示檔名的程式寫好,並將本小節的程式整合在一起。

如果只想顯示某些類型的檔案,則可以加入條件判斷式進行篩選:

..................前略.........................
<table border=0><tr>
<? foreach ($files as $fname) {
    if (strtoupper(substr($fname,0,3))=="JPG") {?>
        <td>
        ..................略......................
        </td>
<?  
        $i++;
        if ($i % 5==0) {?>
            </tr><tr>
<?      }
    }
}?>
</tr></table>

作業9:請改寫上面的程式:移除表格,將 *.mid 音樂檔篩選出來,擺放在陣列 music 中,用 rand(0,$i) 取亂數做為索引值,隨機產生背景音樂(<bgsound src="">)

 

MySQL 資料庫管理

MySQL 是網路上最多人使用的免費資料庫,要使用 MySQL 資料庫你必須先修改 php.ini 並事先將資料庫的使用者及其權限設定好,由於 PHP 函式庫會透過 TCP 封包連接資料庫,如果資料庫位於另一台主機,請務必檢查防火牆設定,是否有開放 3306 Port。

設定 MySQL 權限的方法是:

mysqladmin -u root password 您要使用的密碼 //資料庫與網頁在同一台
mysqladmin -h root@程式所在主機 -u root password 您要使用的密碼

設定好後請修改 php.ini:

[MySQL]
; Allow or prevent persistent links.
mysql.allow_persistent = On

; Maximum number of persistent links. -1 means no limit.
mysql.max_persistent = -1

; Maximum number of links (persistent + non-persistent). -1 means no limit.
mysql.max_links = -1

; Default port number for mysql_connect(). If unset, mysql_connect() will use
; the $MYSQL_TCP_PORT or the mysql-tcp entry in /etc/services or the
; compile-time value defined MYSQL_PORT (in that order). Win32 will only look
; at MYSQL_PORT.
mysql.default_port =

; Default socket name for local MySQL connects. If empty, uses the built-in
; MySQL defaults.
mysql.default_socket =

; Default host for mysql_connect() (doesn't apply in safe mode).
mysql.default_host =

; Default user for mysql_connect() (doesn't apply in safe mode).
mysql.default_user =

; Default password for mysql_connect() (doesn't apply in safe mode).
; Note that this is generally a *bad* idea to store passwords in this file.
; *Any* user with PHP access can run 'echo get_cfg_var("mysql.default_password")
; and reveal this password! And of course, any users with read access to this
; file will be able to reveal the password as well.
mysql.default_password =

; Maximum time (in secondes) for connect timeout. -1 means no limit
mysql.connect_timeout = 60

; Trace mode. When trace_mode is active (=On), warnings for table/index scans an
[21~; SQL-Errors will be displayed.
mysql.trace_mode = Off

資料庫的管理透過命令列模式來進行很不方便,因此最好是安裝 phpmyadmin 透過網頁介面直接管理,裝好 phpmyadmin 後必須修改程式碼讓程式能連上 MySQL 資料庫。請打開 /var/www/html/phpmyadmin/config.inc.php 修改底下內容:

/* Server www.xxps.tp.edu.tw (config:root) [1] */
$i++;
$cfg['Servers'][$i]['host'] = 'localhost';
$cfg['Servers'][$i]['extension'] = 'mysqli';
$cfg['Servers'][$i]['connect_type'] = 'tcp';
$cfg['Servers'][$i]['compress'] = false;
$cfg['Servers'][$i]['controluser'] = 'root';
$cfg['Servers'][$i]['controlpass'] = '設定在MySQL的root密碼';
$cfg['Servers'][$i]['auth_type'] = 'http';

//$cfg['Servers'][$i]['auth_type'] = 'config';
//$cfg['Servers'][$i]['user'] = 'root';
//$cfg['Servers'][$i]['password'] = '設定在MySQL的root密碼';
$cfg['Servers'][$i]['verbose'] = '資料庫所在的主機名稱';
/* End of servers configuration */

連接資料庫

在 PHP 中要連上資料庫主機,可以使用 mysql_connect() 或 mysql_pconnect() 函式:

$conn=mysql_connect($db_ip,$db_user,$db_pw);

此兩個函式用法相同,但 mysql_pconnect() 會建立持續性連線(keep alive)在程式執行完畢後,連線會被保留等待下一次的利用,而不會所佔用的釋放記憶體,如果整個站台都會用到同一資料庫主機,使用 mysql_pconnect() 能帶來比較好的執行效率。

連線建立以後,必須指定要連結的資料庫名稱,習慣上同一個應用程式會用到的所有資料表,會放在同一個資料庫中,由於一個站台會用到很多個應用程式,因此就會有很多個資料庫。指定要操作的資料庫可以使用 mysql_select_db() 函式:

mysql_select_db("資料庫名稱",$conn);

透過 SQL 語言操作資料表

SQL 是「結構化查詢語言」(Structured Query Language)的簡稱,是由 IBM 公司於 1970 年代所發展出來,用於關連式資料庫 (Relational Databases) 當中的一種資料庫查詢語言,利用 SQL 可以用來定義資料庫結構、指定資料庫表格與欄位型態與長度、新增資料、修改資料、刪除資料、查詢資料,以及建立各重複雜的表格關連,成為一個查詢資料庫的標準語言。

雖然各家資料庫所提供的 SQL 語法在功能上會略有差異,但基本的功能都是符合于 ANSI SQL-92。SQL 的設計基本上是模仿英文的自然語法,因此在入門上較為容易。

SQL 指令最常用的四個基本查詢動作,即:查詢、新增、修改、刪除,因此將來所設計的 PHPP 程式,也通常會包含這四種功能。

(一)、查詢資料:若要查詢資料庫的資料,使用的 SQL 指令是「SELECT」,基本語法如下:

SELECT 欄位名稱1,欄位名稱2... FROM 資料表名稱1,資料表名稱2... WHERE 條件式 ORDER BY 欄位名稱1 DESC(降冪排序),欄位名稱2...

範例如下:

$sql = "SELECT * FROM emp ORDER BY sex DESC,name";

→排序:區分為降冪與升冪兩種,上例為選取 emp 這個資料表的所有內容,並先照性別降冪排序(男生排前面)再依照姓名筆劃排序

在進行條件比對時,如果比對的欄位是數字欄位,則不加識別符號,例如:WHERE money >= 3000,如果該欄位為文字欄位,必須使用 ' 來標示,例如:WHERE name = '張三',如果是比對日期欄位,則用 # 來標示,例如:WHERE post_date >= #2003/04/15#

$sql = "SELECT * FROM emp WHERE name LIKE '李%'";

→文字欄位模糊比對:使用 LIKE 敘述配合 % 符號來比對,上例用來比對 name 欄位中第一個字是"李"的所有記錄,如果要比對結尾是否為 ""where NAME Like '%憲',比對是否含有 ""where NAME Like '%忠%'

$sql = "SELECT * FROM class WHERE year='$stryear' AND seme='$strseme' AND LEFT(classno,1) IN ('3','4','5','6') ";

→集合比對:用來比對欄位的值是否為集合中所列舉的其中一個,這可以運用在任何資料類型的欄位上,上例選取 class 資料表內的所有資料但是 year 必須等於 $stryear 變數的值(學年),且 seme 必須等於 $strseme 變數的值(學期),且 classno (班級代號)為 3,4,5,6 其中任一個字元

作業10:請找出底下的語法哪裡錯誤:
$sql = "select * from SEMP where NAME = '$firstname%' and EMPID=$strid";

(二)、合併查詢:當資料分布在多個資料表中,就必須使用合併查詢,合併查詢意思是將兩個資料表先合併成一個,然後再進行條件過濾,篩選出想找的資料記錄,依照合併方式的不同,又分為外部合併(outter join)和內部合併(inner join),假設有兩個資料表,前者有十筆紀錄,後者也有十筆紀錄,兩者以學號來互相關聯,進行外部合併時,會將兩個資料表相乘,得到一個 100 筆紀錄的新資料表,接著再逐筆過濾學號是否一樣;如果進行內部合併,則先以學號作為索引,將兩資料表中相同索引的紀錄合併為一筆,最後得到一個十筆紀錄的資料表。

由於內部合併比較省記憶體,而且如果欄位索引事先已經做好,其合併效能遠高於外部合併,因此底下僅介紹內部合併的方法:

$sql = "SELECT a.classno, a.seat, b.name, b.sex FROM class_std a JOIN student b on a.stdno=b.stdno WHERE a.year='$stryear' AND a.seme='$strseme' AND right(b.birthday,4)='$today' ORDER BY a.classno";

→把 class_std 這個資料表命名為 a,把 student 這個資料表命名做 b,以 a、b 資料表裡面都有的 stdno 欄位(學號)當作索引來合併資料表。並篩選出 a 的學年期和現在的學年期相等,而且 b 資料表上的 birthday 欄位的後四碼是今天,找出來以後照年級來排序。

作業11:如果要從 student 學生學籍資料表及 class_std 學生編班資料表,找出今天的所有壽星並顯示就讀班級和姓名, SQL 命令應該怎麼寫?(提示:可用 SQL 企業管理員中的資料表查詢精靈來自動產生)

(三)、新增資料:若要新增資料庫的資料,使用的 SQL 指令是「INSERT」,基本語法如下:

INSERT INTO 資料表名稱(欄位名稱1,欄位名稱2,...) VALUES ('欄位1的資料','欄位2的資料',...)

範例如下:

$sql = "INSERT INTO teacher (name,phone,address,birth) VALUES ('陳自強','28975966','新民路2號','19940101')";

→插入資料到 teacher 資料表中,在 name 欄位填入字串:黃自強,phone 欄位填入:28975966,依此類推。

(四)、修改資料: 若要修改資料庫的資料,使用的 SQL 指令是「UPDATE」,基本語法如下:

UPDATE 資料表名稱 SET 欄位名稱1='欄位1的資料',欄位名稱2='欄位2的資料',... WHERE 條件式

範例如下:

$sql = "UPDATE  teacher SET name='黃自強' WHERE phone='28975966' AND birth='640101' ";

→找到 phone 欄位資料為 28975966 且 birth 欄位為 640101 的紀錄,將該紀錄 name 欄位改為黃自強。

(四)、刪除資料: 若要刪除資料庫的資料,使用的 SQL 指令是「DELETE」,基本語法如下:

DELETE FROM 資料表名稱 WHERE 條件式

範例如下:

$sql = "DELETE FROM award_question WHERE id=".$id;

→在 award_question 資料表中找到 id 欄位等於 id 變數值的記錄,將該紀錄刪除。

一次刪除多筆資料,範例如下:

$sql = "DELETE FROM emp WHERE usename=password";

→在 emp 資料表中找到 username 欄位資料與 password 欄位資料相同的記錄,將符合條件的所有紀錄刪除。

(五)、注意事項:

進行資料處理

SQL 命令處理完成後,必須將命令字串傳送給 MySQL 資料庫系統執行,以便取得處理結果:

$rs_query=mysql_query($sql,$conn);

處理結果會暫存在記憶體中,並傳回記憶體位址,上述範例把記憶體位址存放在 $rs_query 變數中。如果所進行的資料處理不是資料查詢(SELECT),也可以不使用變數去承接傳回值:

mysql_query($sql,$conn);

相反的如果是資料查詢,必須用 mysql_fetch_array() 函式取得資料紀錄,萬一資料紀錄不存在,貿然去處理資料將會造成程式中斷,因此必須先進行檢查:

$rs=mysql_fetch_array($rs_query);
if ($rs==0) echo "查無學生缺席紀錄!";

如果傳回的資料有很多筆,則必須使用不定量迴圈來處理:

$rs=mysql_fetch_array($rs_query);
if ($rs==0)
    echo "查無學生請假紀錄!";
else
    while ($rs!=0) {
        echo "<tr><td>學生姓名:".$rs['name']."</td>";
        echo "<td>請假日期:".$rs['log_date']."</td></tr>";
        $rs=mysql_fetch_array($rs_query);
    }

 

打造網頁應用程式平台

好的網頁應用程式,除了有跟參觀者互動的單元以外,最好也包含管理員的操作介面。在設計程式時必須考慮如何區分一般參觀者和管理員的身分,如果開發的程式不只一套,那就可能發生不同程式有不同管理員的可能性,這種情況就無法只靠會員身份來決定權限,而必須另有一套管理權限的辦法。底下就嘗試來開發一個完整而富有彈性的的會員和權限管理系統,也在設計過程中練習以【整個站台】的觀點來思考網頁應用程式的設計。


程式開發流程,可以安排成:

系統分析 → 資料庫分析 → 設計網頁介面 → 填入 PHP 程式碼 → 實際測試程式

  1. 系統分析先決定此次設計要達到的目標,要設計哪些功能。

  2. 功能的架構和流程為何?可以隨手畫在紙上幫助思考,哪怕是餐巾紙。

  3. 根據程式需求,設計資料表,決定資料表名稱、欄位名稱、各欄位的資料型態、索引鍵值...等相關設定。

  4. 先用 Expression Web(FrontPage)等 WYSWYG 網頁編輯軟體把會用到的網頁純 HTML 使用者介面設計好,例如:清單、新增、刪除、修改等網頁。

  5. 請美工人員在純 HTML 網頁上進行美工配色等美化工程。

  6. 開始撰寫(填入)程式碼。

  7. 測試及修改程式碼。

1.系統分析

系統分析通常要包含案例分析、功能分析、流程分析、狀態分析、時間軸分析......等等。您可能需要使用一些分析工具,例如:UML。當然不透過工具也是可以進行分析,但您仍然必須將想法寫下來,以方便發現矛盾和解決衝突。

本系統的功能表列如下:

  1. 會員必須能從網頁直接註冊

  2. 會員註冊後自動生效

  3. 會員必須隸屬於某個群組

  4. 會員資料與帳號管控分成兩個資料表管理,為將來可能需使用的欄位提供彈性

  5. 權限管理可分程式個別授予

  6. 權限能授與給個人或群組

  7. 權限應能區分不同等級

  8. 帳號登入必須在用戶端編密後再送回伺服器,以防止密碼外洩

  9. 帳號登入失敗超過三次,帳號自動鎖定

2.功能架構

3.資料庫設計

設計資料庫需要事前規劃,依據系統分析的結果,本系統會用到的資料表共有四個,包括:帳號密碼(user)、使用者資訊(user_info)、群組(group)、應用程式(packets)、授權設定(permset),詳細資訊如下:

資料表:user
欄位名稱 資料型態 長度 NULL 備註
uid varchar 20 帳號
pwd varchar 32 密碼
gid int 10 隸屬群組
last_logon datetime   最後一次登入時間
last_ip varchar 15 最後一次登入地點
error_times tinyint 4 登入失敗次數
locked tinyint 1 是否鎖定
create_date datetime   帳號建立日期

 

資料表:user_info
欄位名稱 資料型態 長度 NULL 備註
uid varchar 20 帳號
uname varchar 50 真實姓名
sex tinyint 1 姓名
ID varchar 10 身分證字號
birthday date   生日
city varchar 20 居住地
address varchar 200 詳細地址
tel varchar 20 電話號碼
mobile varchar 20 手機號碼
email varchar 200 電子郵件信箱
homepage varchar 200 個人網頁網址
dep varchar 200 服務機構
school varchar 200 就讀學校
grade varchar 10 就讀年級
class varchar 10 就讀班級

 

資料表:group
欄位名稱 資料型態 長度 NULL 備註
gid int 11 群組代號
gname varchar 50 群組名稱
admin varchar 20 建立該群組的管理者
create_date datetime   群組建立日期

 

資料表:packets
欄位名稱 資料型態 長度 NULL 備註
packet_code varchar 20 模組代號
packet_name varchar 100 模組名稱
packet_perm varchar 5 模組權限碼:ABCD 代表四種不同權限
perm_desc varchar 200 模組詳細說明

 

資料表:permset
欄位名稱 資料型態 長度 NULL 備註
uid varchar 20 帳號或群組代號
type tinyint 1 型態:0->使用者 1->群組
packet_code varchar 20 模組名稱
permset char 1 權限碼
admin varchar 20 授予權限之管理員
create_date datetime   授權日期

資料表一開始就使用 utf-8 編碼,請不要改成 big5 編碼,因為使用 utf-8 才能兼容於全世界各國語言。要設計這幾個資料表,請使用瀏覽器連到 phpmyadmin,首先先建立一個資料庫 web:

接著在該資料庫中,新增資料表請輸入資料表名稱,欄位數量待會兒還可以修改:

依序將欄位名稱及相關屬性輸入到表格中,欄位新增完成後,必須設定索引鍵值:

資料表建立後,可以將系統預設的資料紀錄先新增進去,要建立的資料包含:預設群組(管理員及一般使用者)、預設管理員、預設模組(account、perm)、預設授權(將 account 和 perm 授權給預設管理員)

預設資料輸入完畢後,必須將資料庫結構及預設資料整個輸出成一個 SQL 指令稿,將來可以利用此指令稿來進行系統移轉,請從左側選單點選資料庫 web,然後點選上方的輸出鈕:

將網頁捲動到最下面,把下載儲存打勾,按執行按鈕後取得 SQL 指令稿:

4.網頁介面設計

根據程式 架構來設計製作各個頁面:

PHP 檔名

功能
login.php 會員登入頁面
register.php 新會員註冊頁面
passin.php 修改密碼頁面
empudform.php 修改會員資料頁面
manager.php 管理後台頁面
grplist.php 群組管理頁面
grpaform.php 新增群組頁面
grpudform.php 修改群組頁面
grpdelete.php 刪除群組程式,不需要頁面
emplist.php 會員管理頁面
empaform.php 新增會員頁面
empudform.php 修改會員頁面
empqform.php 查詢會員頁面
empdelete.php 刪除會員程式,不需要頁面
resetpass.php 重設會員密碼程式,不需要頁面
resetall.php 重設所有密碼程式,不需要頁面
lock.php 鎖定會員帳號程式,不需要頁面
unlock.php 解除鎖定程式,不需要頁面
priv_list.php 模組權限管理頁面
priv_add.php 新增模組權限頁面
priv_edit.php 修改模組權限頁面
grant_list.php 授權管理頁面
grant_add.php 新增授權頁面
grant_edit.php 修改授權頁面

login.php
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<link rel="stylesheet" href="/main.css" type="text/css">
<TITLE>登入系統</TITLE>
</head>
<body>
<center><p><br><BR></p>
<table border="0" cellpadding="0" cellspacing="0" width="500" height="210" id="table8">
<tr>
<td>
<table border="0" width="100%" id="table9" cellspacing="0" cellpadding="0" height="23">
<tr>
<td width="3%" height="23" nowrap>
<img border="0" src="/images/windows/1.gif" width="24" height="23"></td>
<td width="88%" background="/images/windows/2.gif" height="23">
<font color="#808080"><b>請先登入系統</b></font></td>
<td width="67" height="23" background="/images/windows/3.gif" nowrap>
 </td>
</tr>
</table>
<table border="0" width="100%" id="table10" cellspacing="0" cellpadding="0" height="210">
<tr>
<td width="4" background="/images/windows/4.gif"></td>
<td width="20"></td>
<td width="651" valign="middle">
<FORM>
<div align="center">
<table border="0" cellpadding="0" cellspacing="0" width="44%" height="180" id="table12">
<tr>
<td align="center"><font color="#808080"><b>帳號</b></font></td>
<td><font color="#808080"><INPUT TYPE="TEXT" SIZE="11" style="font-weight: 700"></font></td>
</tr>
<tr>
<td align="center"><font color="#808080"><b>密碼</b></font></td>
<td><font color="#808080"><INPUT TYPE="PASSWORD" SIZE="11" style="font-weight: 700"></font></td>
</tr>
<tr>
<td></td>
<td><INPUT TYPE="button" VALUE="登入" style="font-family: 標楷體; font-size: 18pt; color: #666666"></td>
</tr>
</table>
</div>
<p align="center"><font color="#808080">註冊成為會員,您才能參與冒險遊戲任務→</font><INPUT TYPE="button" VALUE="註冊會員">
<p align="center"><font color="#808080">變更您的密碼:請先輸入帳號密碼後再按→</font><INPUT TYPE="BUTTON" VALUE="變更密碼">
<p align="center"><font color="#808080">修改個人資料:請先輸入帳號密碼後再按→</font><INPUT TYPE="BUTTON" VALUE="修改資料">
</FORM>
</td>
<td width="20"></td>
<td width="5" height="163" background="/images/windows/4.gif"></td>
</tr>
</table>
<table border="0" width="100%" id="table11" cellspacing="0" cellpadding="0" height="26">
<tr>
<td width="3%"><img border="0" src="/images/windows/6.gif" width="24" height="26"></td>
<td width="665" background="/images/windows/7.gif" height="26"></td>
<td width="28" height="26" background="/images/windows/8.gif"></td>
</tr>
</table>
</td>
</tr>
</table>
</center>
</body>
</html>

請將 register.php 頁面複製到 empaform.php
<HTML>
<HEAD><TITLE>會員註冊</TITLE>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<link rel="stylesheet" href="/main.css" type="text/css"></head>
<body style="text-align: center">
<h2 align="center"><font color="#008000">會員註冊</font> </h2>
<FORM NAME="AFORM" ACTION="empadd.php" METHOD="POST">
<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=0>
<TR>
<TD>
<p align="center">
<input type="button" value="寫好了,請送出"> <input type="button" value="回登入畫面">
</TD>
</TR>
</TABLE>
<TABLE BORDER=0>
<TR><TH BGCOLOR="#FFF8DC" ALIGN="left">
<p><FONT COLOR="#0000FF">會員姓名:*</FONT><TD BGCOLOR=#e7e7e7>
<p align="left"><INPUT NAME="uname" SIZE=20 MAXLENGTH=50></th>
</tr>
<TR><TH BGCOLOR="#FFF8DC" ALIGN="left">
<p><FONT COLOR="#0000FF">會員帳號:*</FONT><TD BGCOLOR=#e7e7e7>
<p align="left"><INPUT NAME="uid" SIZE=12 MAXLENGTH=20></th>
</tr>
<TR><TH BGCOLOR="#FFF8DC" ALIGN="left">
<p><FONT COLOR="#0000FF">密  碼:*</FONT><TD BGCOLOR=#e7e7e7>
<p align="left"><INPUT type="password" NAME="pwd1" SIZE=12 MAXLENGTH=12></th>
</tr>
<TR><TH BGCOLOR="#FFF8DC" ALIGN="left">
<p><FONT COLOR="#0000FF">確認密碼:*</FONT><TD BGCOLOR=#e7e7e7>
<p align="left"><INPUT type="password" NAME="pwd2" SIZE=12 MAXLENGTH=12></th>
</tr>
<TR><TH BGCOLOR="#FFF8DC" ALIGN="left">
<p><FONT COLOR="#0000FF">性  別:*</FONT><TD BGCOLOR=#e7e7e7>
<p align="left"><input type="radio" value="1" name="sex">男 <input type="radio" value="0" name="sex" checked>女</font></th>
</tr>
<TR><TH BGCOLOR="#FFF8DC" ALIGN="left">
<p><FONT COLOR="#0000FF">身分證號:*</FONT><TD BGCOLOR=#e7e7e7>
<p align="left"><INPUT NAME="ID" SIZE=10 MAXLENGTH=10></th>
</tr>
<TR><TH BGCOLOR="#FFF8DC" ALIGN="left">
<p><FONT COLOR="#0000FF">生  日:*</FONT><TD BGCOLOR=#e7e7e7>
<p align="left"><INPUT NAME="birthday" SIZE=12 MAXLENGTH=12></th>
</tr>
<TR><TH BGCOLOR="#FFF8DC" ALIGN="left">
<p><FONT COLOR="#0000FF">縣  市:*</FONT><TD BGCOLOR=#e7e7e7 align="left">
<INPUT NAME="city" SIZE=20 MAXLENGTH=20></TH>
</TR>
<TR><TH BGCOLOR="#FFF8DC" ALIGN="left">
<p><FONT COLOR="#0000FF">聯絡地址:</FONT><TD BGCOLOR=#e7e7e7 align="left">
<INPUT NAME="address" SIZE=100 MAXLENGTH=200></TH>
</TR>
<TR><TH BGCOLOR="#FFF8DC" ALIGN="left">
<p><FONT COLOR="#0000FF">聯絡電話:</FONT><TD BGCOLOR=#e7e7e7 align="left">
<INPUT NAME="tel" SIZE=20 MAXLENGTH=20></TH>
</TR>
<TR><TH BGCOLOR="#FFF8DC" ALIGN="left">
<p><FONT COLOR="#0000FF">E-MAIL:*</FONT><TD BGCOLOR=#e7e7e7 align="left">
<p> <INPUT NAME="email" SIZE=40 MAXLENGTH=200></TH>
</TR>
<TR><TH BGCOLOR="#FFF8DC" ALIGN="left">
<p><FONT COLOR="#0000FF">就讀學校:</FONT><TD BGCOLOR=#e7e7e7 align="left">
<INPUT NAME="school" SIZE=40 MAXLENGTH=200></TH>
</TR>
<TR><TH BGCOLOR="#FFF8DC" ALIGN="left">
<p><FONT COLOR="#0000FF">就讀班級:</FONT><TD BGCOLOR=#e7e7e7 align="left">
<INPUT NAME="grade" SIZE=5 MAXLENGTH=10>年<INPUT NAME="class" SIZE=5 MAXLENGTH=10>班</TH>
</TR>
</TABLE>
</FORM>
</CENTER>
</BODY>
</HTML>

passin.php
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<link rel="stylesheet" href="/main.css" type="text/css">
</head>
<body>
<center><h1><font color="#008000" face="標楷體">變更密碼</font></h1>
<form>
<table cellpadding=5>
<tr><td colspan=2 align=right bgcolor=fff5dc>
<p align="center"><b><font color="#0000FF">第一次進入務必修改密碼</font></b>
<tr><td align=right bgcolor=fff5dc><b><font color=red>
輸入新密碼</font></b><td bgcolor=e7e7e7><input type='password' name='newpass' size="20">
<tr><td align=right bgcolor=fff5dc><font color=red><b>請再確認一次新密碼</b></font>
<td bgcolor=e7e7e7><input type='password' name='chknewpass' size="20"><br>
</table>
<input type="button" value="確定"> <input type="reset" value="重新輸入">
<br>
</form>
</center>
</body>
</html>

empudform.php
<HTML>
<HEAD><TITLE>修改會員資料</TITLE>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" href="/main.css" type="text/css"></head>
<body>
<h2 align="center"><font color="#008000">修改會員資料</font></h2>
<CENTER>
<FORM>
<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=0>
<TR>
<TD>
<input type="button" value="改好了,請送出"> 
<input type="button" value="回帳號管理">
</TR>
</TABLE>
<TABLE BORDER=0>
<TR><TH BGCOLOR="#FFF8DC" ALIGN="right"><FONT COLOR="#0000FF">真實姓名:</FONT></TH>
<TD BGCOLOR=#e7e7e7><INPUT NAME="uname" TYPE="TEXT" SIZE=20 MAXLENGTH=50></TD></TR>
<TR><TH BGCOLOR="#FFF8DC" ALIGN="right"><FONT COLOR="#0000FF">性  別:</FONT></TH>
<TD BGCOLOR=#e7e7e7><input type="radio" value="1" name="sex">男 <input type="radio" value="0" name="sex">女</TD></TR>
<TR><TH BGCOLOR="#FFF8DC" ALIGN="right"><FONT COLOR="#0000FF">身分證字號:</FONT></TH>
<TD BGCOLOR=#e7e7e7><INPUT NAME="ID" TYPE="TEXT" SIZE=10 MAXLENGTH=10></TD></TR>
<TR><TH BGCOLOR="#FFF8DC" ALIGN="right"><FONT COLOR="#0000FF">生日:</FONT></TH>
<TD BGCOLOR=#e7e7e7><INPUT NAME="birthday" TYPE="TEXT" SIZE=12 MAXLENGTH=12></TD></TR>
<TR><TH BGCOLOR="#FFF8DC" ALIGN="right"><FONT COLOR="#0000FF">縣市:</FONT></TH>
<TD BGCOLOR=#e7e7e7><INPUT NAME="city" TYPE="TEXT" SIZE=20 MAXLENGTH=20></TD></TR>
<TR><TH BGCOLOR="#FFF8DC" ALIGN="right"><FONT COLOR="#0000FF">聯絡地址:</FONT></TH>
<TD BGCOLOR=#e7e7e7><INPUT NAME="address" TYPE="TEXT" SIZE=100 MAXLENGTH=200></TD></TR>
<TR><TH BGCOLOR="#FFF8DC" ALIGN="right"><FONT COLOR="#0000FF">聯絡電話:</FONT></TH>
<TD BGCOLOR=#e7e7e7><INPUT NAME="tel" TYPE="TEXT" SIZE=20 MAXLENGTH=20></TD></TR>
<TR><TH BGCOLOR="#FFF8DC" ALIGN="right"><FONT COLOR="#0000FF">EMAIL:</FONT></TH>
<TD BGCOLOR=#e7e7e7><INPUT NAME="email" TYPE="TEXT" SIZE=40 MAXLENGTH=200></TD></TR>
<TR><TH BGCOLOR="#FFF8DC" ALIGN="right"><FONT COLOR="#0000FF">任職單位:</FONT></TH>
<TD BGCOLOR=#e7e7e7><INPUT NAME="dep" TYPE="TEXT" SIZE=40 MAXLENGTH=200></TD></TR>
<TR><TH BGCOLOR="#FFF8DC" ALIGN="right"><FONT COLOR="#0000FF">就讀學校:</FONT></TH>
<TD BGCOLOR=#e7e7e7><INPUT NAME="school" TYPE="TEXT" SIZE=40 MAXLENGTH=200></TD></TR>
<TR><TH BGCOLOR="#FFF8DC" ALIGN="right"><FONT COLOR="#0000FF">就讀班級:</FONT></TH>
<TD BGCOLOR=#e7e7e7><INPUT NAME="grade" TYPE="TEXT" SIZE=5 MAXLENGTH=10>年<INPUT NAME="class" TYPE="TEXT" SIZE=5 MAXLENGTH=10>班</TD></TR>
</TABLE>
</FORM>
</CENTER>
</BODY>
</HTML>

manager.php
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>管理後臺</title>
</head>
<body>
<h1 align="center"><font face="標楷體">管理後臺</font></h1>
<table align="center" border="0" width="80%" id="table1" cellspacing="3" cellpadding="4">
<tr>
<td colspan="5"><b>資料庫管理</b></td>
</tr>
<tr><td><a href="/phpMyAdmin/"><font face="標楷體" size="4">phpMyAdmin</font></a></td><td> </td><td> </td><td> </td><td> </td></tr>
<tr><td colspan="5"> </td></tr>
<tr><td colspan="5"><b>系統管理</b></td></tr>
<tr>
<td><a href="/account/login.php"><font face="標楷體" size="4">重新登入</font></a></td>
<td><a href="/account/grplist.php"><font face="標楷體" size="4">群組管理</font></a></td>
<td><a href="/account/emplist.php"><font face="標楷體" size="4">會員管理</font></a></td>
<td><a href="/perm/priv_list.php"><font face="標楷體" size="4">權限管理</font></a></td>
<td><a href="/perm/grant_list.php"><font face="標楷體" size="4">授權設定</font></a></td>
</tr>
<tr>
<td colspan="5"> </td>
</tr>
</table>
</body>
</html>

grouplist.php
<HTML>
<HEAD>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<TITLE>群組管理</TITLE>
<link rel="stylesheet" href="/main.css" type="text/css">
</head>
<body>
<h2 align="center"><font color="#008000">群組管理</font> </h2>
<CENTER>
<TABLE cellpadding=0>
<TR><TD><A HREF="grpaform.php">新增群組</A> | <A HREF="/public/manager.php">回管理後臺</A></td></TR>
</TABLE>
<TABLE BORDER=0 BGCOLOR="#FFF8DC" cellspacing=1>
<TR>
<TH><FONT COLOR="red">群組編號</FONT>
<TH><FONT COLOR="red">群組名稱</FONT>
<TH><FONT COLOR="red">管理員</FONT>
<TH><FONT COLOR="red">建立日期</FONT>
<TH><FONT COLOR="red">管理</FONT>
</TR>
<TR>
<TD><hr align="left">
<TD><hr align="left">
<TD><hr align="left">
<TD><hr align="left">
<TD><hr align="left">
</TR>
<tr>
<TD> </TD>
<TD> </TD>
<TD> </TD>
<TD> </TD>
<TD><A HREF="grpudform.php">修改</A> | <A HREF="grpdelete.php"><font color="#FF0000">刪除</font></A></TD>
</tr>
</TABLE>
<TABLE cellpadding=0>
<TR>
<TH COLSPAN=3 ALIGN='LEFT'><FONT SIZE=-1 COLOR='green'>&lt&nbsp第 頁 / 共 頁&nbsp&gt</FONT></TH>
<TH ALIGN='RIGHT'>
<FONT SIZE=-1></FONT><A HREF="grplist.php">上一頁</A></font>
<FONT SIZE=-1></FONT><A HREF="grplist.php">下一頁</A></font>
</TH></TR>
</TABLE>
</CENTER>
</BODY>
</HTML>

請將 grpaform.php 頁面複製到 grpudform.php
<HTML>
<HEAD><TITLE>新增群組</TITLE>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" href="/main.css" type="text/css"></head>
<body style="text-align: center">
<h2 align="center"><font color="#008000">新增群組</font> </h2>
<FORM>
<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=0>
<TR>
<TD>
<p align="center"><input type="button" value="寫好了,請送出"> <input type="button" value="回群組管理"></p>
</TD>
</TR>
</TABLE>
<TABLE BORDER=0>
<TR><TH BGCOLOR="#FFF8DC" ALIGN="right">
<p align="center"><FONT COLOR="#0000FF">群組名稱:*</FONT><TD BGCOLOR=#e7e7e7>
<p align="left"><INPUT NAME="gname" SIZE=12 MAXLENGTH=20></th>
</tr>
</TABLE>
</FORM>
</CENTER>
</BODY>
</HTML>

emplist.php
<HTML>
<HEAD>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<TITLE>會員管理</TITLE>
<link rel="stylesheet" href="/main.css" type="text/css">
</head>
<body>
<h2 align="center"><font color="#008000">會員管理</font> </h2>
<CENTER>
<TABLE cellpadding=0>
<TR><TD>
<A HREF='empqform.php'>進階搜尋</A> | <A HREF="empaform.php">新增會員</A> | <A HREF="resetall.php">重設所有會員密碼</A> | <A HREF="/public/manager.php">回管理後臺</A>
</td></TR>
</TABLE>
<TABLE BORDER=0 BGCOLOR="#FFF8DC" cellspacing=1>
<TR>
<TH><FONT COLOR="red">姓名</FONT>
<TH><FONT COLOR="red">性別</FONT>
<TH><FONT COLOR="red">身分證字號</FONT>
<TH><FONT COLOR="red">縣市</FONT>
<TH><FONT COLOR="red">電話</FONT>
<TH><FONT COLOR="red">電郵</FONT>
<TH><FONT COLOR="red">任職單位</FONT>
<TH><FONT COLOR="red">就讀學校</FONT>
<TH><FONT COLOR="red">就讀年班</FONT>
<TH><FONT COLOR="red">管理</FONT>
</TR>
<TR>
<TD><hr align="left">
<TD><hr align="left">
<TD><hr align="left">
<TD><hr align="left">
<TD><hr align="left">
<TD><hr align="left">
<TD><hr align="left">
<TD><hr align="left">
<TD><hr align="left">
<TD><hr align="left">
</TR>
<tr>
<TD> </TD>
<TD> </TD>
<TD> </TD>
<TD> </TD>
<TD> </TD>
<TD> </TD>
<TD> </TD>
<TD> </TD>
<TD> 年 班</TD>
<TD>
<A HREF="empudform.php">修改</A> | <A HREF="empdelete.php"><font color="#FF0000">刪除</font></A> | <A HREF="resetpass.php"><font color="#FF0000">重設密碼</font></A> | <A HREF="lock.php"><font color="#FF0000">鎖定</font></A> | <A HREF="unlock.php"><font color="#FF0000">解除鎖定</font></A>
</TD></tr>
</TABLE>
<TABLE cellpadding=0>
<TR>
<TH COLSPAN=3 ALIGN='LEFT'><FONT SIZE=-1 COLOR='green'>&lt&nbsp第 頁 / 共 頁&nbsp&gt</FONT></TH>
<TH ALIGN='RIGHT'>
<FONT SIZE=-1></FONT><A HREF="emplist.php">上一頁</A></font>
<FONT SIZE=-1></FONT><A HREF="emplist.php">下一頁</A></font>
</TH></TR>
</TABLE>
</CENTER>
</BODY>
</HTML>

empqform.php
<HTML>
<HEAD><TITLE>會員查詢</TITLE>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" href="/main.css" type="text/css">
</head>
<body>
<h2 align="center"><font color="#008000">會員查詢</font> </h2>
<CENTER>
</h2>
<FORM>
<p><input type="submit" value="搜尋"> <input type="reset" value="重新輸入"></p>
<TABLE BORDER=0>
<TR>
<TH BGCOLOR="#FFF8DC" ALIGN="right"><FONT COLOR="red">會員姓名</FONT><FONT COLOR="green">:</FONT></TH>
<TD BGCOLOR=#e7e7e7><INPUT NAME="username" TYPE="TEXT" SIZE=12 MAXLENGTH=10></TD>
</TR>
<TR>
<TH BGCOLOR="#FFF8DC" ALIGN="right"><FONT COLOR="red">縣  市</FONT><FONT COLOR="green">:</FONT></TH>
<TD BGCOLOR=#e7e7e7><SELECT NAME="depdesc"></SELECT></TD>
</TR>
<TR>
<TH BGCOLOR="#FFF8DC" ALIGN="right"><FONT COLOR="red">帳號名稱</FONT><FONT COLOR="green">:</FONT></TH>
<TD BGCOLOR=#e7e7e7><INPUT NAME="account" TYPE="TEXT" SIZE=16 MAXLENGTH=20></TD>
</TR>
<TR>
<TH BGCOLOR="#FFF8DC" ALIGN="right"><FONT COLOR="red">E-MAIL</FONT><FONT COLOR="green">:</FONT></TH>
<TD BGCOLOR=#e7e7e7><INPUT NAME="email" TYPE="TEXT" SIZE=50 MAXLENGTH=255></TD>
</TR>
</TABLE><BR>
</FORM>
</CENTER>
</BODY>
</HTML>

請將 priv_list.php 頁面複製到 grant_list.php
<HTML>
<HEAD>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<link rel="stylesheet" href="/main.css" type="text/css">
<TITLE>程式權限管理列表</TITLE>
</head>
<body>
<h1><center><font color="#008000" face="標楷體">程式授權設定</font></h1>
<FORM>
<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=0>
<TR>
<TD WIDTH=100><A HREF="priv_add.php">新增程式套件</A>
<TD WIDTH=100><A HREF="/public/manager.php">回管理後臺</A>
</TR>
</TABLE>
<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=3 BGCOLOR=#FFF8DC>
<TR>
<td VALIGN="top" BGCOLOR="#FFF8DC" WIDTH=2></td>
<TH valign="bottom"><FONT COLOR=red size=2>程式<br>代號</FONT><br><hr align="left">
<TH valign="bottom"><FONT COLOR=red size=2>程式<br>名稱</FONT><br><hr align="left">
<TH valign="bottom"><FONT COLOR=red size=2>權限<br>代碼</FONT><br><hr align="left">
<TH valign="bottom"><FONT COLOR=red size=2>權限<br>說明</FONT><br><hr align="left">
<td VALIGN="top" BGCOLOR="#FFF8DC" WIDTH=2></td>
</TR>
<TR>
<TD VALIGN='top' BGCOLOR='#FFF8DC' WIDTH=2></TD>
<TD VALIGN='top'><A HREF='priv_edit.php'>程式模組代號</A></td>
<TD VALIGN='top'><font size=2></font>程式模組名稱</td>
<TD VALIGN='top'><font color=blue>權限代碼</td>
<TD VALIGN='top'>權限說明</td>
<td VALIGN='top' BGCOLOR='#FFF8DC' WIDTH=2></td>
</TR>
</TABLE>
<br>
</FORM>
</CENTER>
</BODY>
</HTML>

請將 priv_add.php 頁面複製到 priv_edit.php
<HTML>
<HEAD><TITLE>新增程式權限</TITLE>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<link rel="stylesheet" href="/main.css" type="text/css"></head>
<body>
<center><h1><font color="#008000" face="標楷體">新增程式權限</font></h1>
<FORM>
<TABLE BORDER=0 CELLSPACING=3 CELLPADDING=3>
<TR>
<TD><A HREF="">完成新增</A>
<TD><A HREF="priv_list.php">程式權限清單</A>
<TD><A HREF="">恢復欄位原資料</A>
</TR>
</TABLE>
<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=3 BGCOLOR=#FFF8DC>
<TR><TH align="left"><FONT COLOR="black">程式代號</FONT><TD><INPUT NAME="packet_code" SIZE=8 MAXLENGTH=20></TR>
<TR><TH align="left"><FONT COLOR="black">程式名稱</FONT><TD><INPUT NAME="packet_name" SIZE=20 MAXLENGTH=20></TR>
<TR><TH align="left"><FONT COLOR="black">權限代碼</FONT><TD><INPUT NAME="packet_perm" SIZE=20 MAXLENGTH=20>(不用間格,例如:ABCD)</TR>
<TR><TH align="left" valign="top"><FONT COLOR="black">程式權限說明</FONT><TD><TEXTAREA ROWS="5" COLS="40" NAME="perm_desc"></textarea></TR>
</TABLE>
<br>
</FORM>
</CENTER>
</BODY>
</HTML>

請將 grant_edit.php 頁面複製到 grant_add.php
<HTML>
<HEAD><TITLE>修改程式授權設定</TITLE></head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<link rel="stylesheet" href="/main.css" type="text/css">
</head>
<body>
<h1>
<center><font color="#008000" face="標楷體">修改程式授權設定</font></center>
</h1>
<FORM>
<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=3 >
<TR><Td align="left"><FONT COLOR="red"><b>程式名稱:</b></FONT> </TR>
<TR><Td align="left" valign='top'><FONT COLOR="black"><b>權限說明</b></FONT> </TR>
<tr><td colspan=2>
<table>
<tr>
<td>群組名稱</td><td><select name="">
<option name="" value="" selected>
</select></td>
<tr><td colspan=3><font color=blue><b>縣市名稱</b></font></td></tr>
<tr>
<td>管理員姓名</td><td><select name="">
<option name="" value="" selected>
</select></td>
</tr>
</table>
</TD></TR>
</TABLE>
<BR>
<TABLE BORDER=0 CELLSPACING=3 CELLPADDING=3>
<TR>
<TD><A HREF="">完成修改</A>
<TD><A HREF="grant_list.php">回到授權設定</A>
<TD><A HREF="">恢復欄位原資料</A>
</TR>
</TABLE>
<br><input type="button" value="授權給新的使用者"><br>
</FORM>
</CENTER>
</BODY>

5.撰寫程式

本範例系統的完整程式碼,可以從這裡下載,不一一表列詳細的程式內容。請自行比對網頁頁面與填入程式碼後的變化,以體會進行網頁應用程式開發的方法。

下載後的程式碼要能運作,必須修改資料庫部份的設定,以紅色字體表示。為了修改方便起見,本系統將所有與建立資料庫連線有關的程式碼,集中寫在 include/database.inc.php 中,凡是需要用到資料庫的程式,都必須先 include 該檔案:

database.inc.php
<?
$db_ip="localhost";
$db_user="root";
$db_pw="123456";
$conn=mysql_connect($db_ip,$db_user,$db_pw);
if (!$conn) header("Location: /public/msg.php?msg=".urlencode("資料庫連線無法建立!請稍後再試!"));
mysql_select_db("web",$conn);
mysql_query("SET NAMES `utf8`",$conn);
?>
permcheck.inc.php
<?
include "../include/database.inc.php";

function get_perm($packet,$uid,$gid) {
    global $conn;
    $perm='';
    if (!empty($uid) && !empty($packet)) {
        $sql="select * from permset where uid='".$gid.
             "' and type=1 and packet_code='".$packet."'";
        $res=mysql_query($sql,$conn);
        $rs=mysql_fetch_array($res);
        if ($rs) $perm=$rs['permset'];

        $sql="select * from permset where uid='".$uid.
             "' and type=0 and packet_code='".$packet."'";
        $res=mysql_query($sql,$conn);
        $rs=mysql_fetch_array($res);
        if ($rs) $perm=$rs['permset'];
    }
    if (empty($perm)) $perm=false;
    return $perm;
}
?>

由於這個系統是為了開發其它網頁應用程式而設計的,因此,把經常要使用的權限判斷程式碼寫成函式,儲存於 permcheck.inc.php(如上方表格所示)。 在進行權限時,程式將先檢查群組權限,然後再檢查個人權限,因此當個人權限與群組權限有衝突時,以個人權限為主。

如果現在要開發一支新的應用程式,是要給一般會員使用,程式可以寫成:(紅色字體的地方應該填入新應用程式所在的位置,身分驗證完成後,會自動將瀏覽器畫面轉送到 jumpto 所指定的頁面)

<?
session_start();
if (!session_is_registered('gid'))
    header("Location: /account/login.php?jumpto=/xxxx/yyyyyy.php");
?>

如果是要給管理員使用的,則在程式開頭加入以下的程式碼:(紅色字體的地方應該填入新應用程式的模組代號)

<?
session_start();
include "../include/permcheck.inc.php";
if (!session_is_registered('gid'))
    header("Location: /account/login.php?jumpto=/xxxx/yyyyyy.php");
$permtype=get_perm('zzzzz',$_SESSION['uid'],$_SESSION['gid']);
if ($permtype!='A')
    header("Location: /public/msg.php?msg=".urlencode("您未被授權使用此程式!"));

?>

為了處理各種錯誤訊息或警告訊息,還另外開發了一支專門顯示系統訊息的程式 public/msg.php:

msg.php
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>系統訊息</title>
</head>
<body bgcolor="#000000" text="#FF0000">
<div align="center">
<center>
<table border="0" width="100%" height="100%" cellspacing="0" cellpadding="0" background="images/bg.jpg">
<tr>
<td width="100%" valign="bottom">
<p align="center"><font face="標楷體" color="#CCFF33" size="6"> <?=$_GET['msg']?> </font></p>
<p align="center"> </p>
<p align="center"> </p>
<p align="center"> </p>
<p align="center"><? if (!empty($_GET['jumpto'])) {?><a href="<?=$_GET['jumpto']?>"><font color=white size=3><? if (!empty($_GET['ln'])) {?><?=$_GET['ln']?><?} else {?>到下一頁<?}?></font></a><?}?></p>
<p align="center"><a href="javascript:history.go(-1)"><font color=white size=3>回上一頁</font></a></p>
<div align="center">
<center>
<table border="1" width="100%" cellspacing="0" bgcolor="#6582CD">
<tr>
<td width="100%"><p align="center"><a href="http://xxxx.xxps.tp.edu.tw"><font color="#FFFFFF">http://xxxx.xxps.tp.edu.tw</font></a></td>
</tr>
</table>
</center>
</div>
</td>
</tr>
</table>
</div>
</body>
</html>

要顯示系統訊息,使用底下程式碼即可:(紅色字體處填入要顯示的訊息)

if (!session_is_registered('gid'))
   header("Location: /public/msg.php?msg=".urlencode("請先登入系統!"));

使用表單登入帳號時,密碼不可以使用明碼傳輸,而必須先用 MD5 加密後再傳送,做法如下:

login.php
<?
session_start();
session_destroy();
?>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<link rel="stylesheet" href="/main.css" type="text/css">
<TITLE>登入系統</TITLE>
<SCRIPT LANGUAGE="Jscript" SRC="/include/md5.js"></SCRIPT>
<SCRIPT>
function handleLogin(flag) {
var pw = document.login.pwd.value;
hash = hex_md5(pw);
document.login.pwd.value = hash;
document.login.act.value = flag;
document.login.submit();
}
</SCRIPT>
</head>
<body>
.................略....................
<FORM NAME="login" METHOD="POST" ACTION="check_pass.php">
<table><tr>
<td>帳號</td>
<td><INPUT TYPE="TEXT" NAME="uid" SIZE="11"></td>
</tr>
<tr>
<td>密碼</td>
<td><INPUT TYPE="PASSWORD" NAME="pwd" SIZE="11"></td>
</tr>
<td><INPUT TYPE="button" VALUE="登入" onClick="handleLogin(0)"></td>
</tr>
</table>
.................略....................

程式先讀取 md5.js 這個 javascript 程式檔案,內含有 md5 加密函式 hex_md5(),當使用者輸入好帳號密碼,並且按下【登入】按鈕後,程式呼叫 handleLogin(0) 函式進行表單資料送出前的處理動作:首先讀取密碼欄位,將原始密碼使用 hex_md5() 函式編密,然後將編密過的字串寫回密碼欄位中,將原始密碼覆蓋掉,然後再送出表單資料。

資料送出後,由 check_pass.php 程式進行身分驗證,除了檢查帳號密碼之外,並在使用者登入成功後,將使用者的個人資訊寫入 $_SESSION 中,以便給其它應用程式進行權限判斷或資料處理使用。如果登入失敗則進行錯誤次數的累計,並在連續錯誤三次之後,自動將帳號鎖定不准登入:

<?
session_start();
include "../include/database.inc.php";

$act=$_POST["act"];
$usrid=str_replace("'","",$_POST["uid"]);
if (empty($usrid)) {
    header("Location: /public/msg.php?
            jumpto=/account/login.php&msg=".urlencode('請輸入您的帳號!'));
    exit;
}
$inputpass=$_POST["pwd"];

$sql="select * from user where uid='".$usrid."'";
$res=mysql_query($sql,$conn);
$rs=mysql_fetch_array($res);
$found=1;
if ($rs==0) {
    $found=0;
} else {
    $gid=intval($rs['gid']);
    if ($inputpass!=$rs['pwd']) {
        $found=0;
        $error_times=intval($rs['error_times'])+1; //計算錯誤次數
        if ($error_times>3) { //大於三次,就鎖定帳號
            $sql="update user set error_times=0,locked=1,last_login='".
                 date("Y-m-d H:l:s")."',last_ip='".
         $_SERVER['remote_addr'].
                 "' where uid='".$usrid."'";
        } else { //小於三次,將次數累計到資料表中
            $sql="update user set error_times=".$error_times.
                 " where uid='".$usrid."'";
        }
        mysql_query($sql,$conn);
    }

}
if ($rs['locked']==1)
    header("Location: /public/msg.asp?msg=".
           urlencode("您的帳號已被鎖定無法登入!")."<br>".
           urlencode("請聯絡管理員處理!"));
if ($found==0)
    header("Location: /public/msg.asp?msg=".
           urlencode("帳號密碼錯誤!")."<br>".
           urlencode("您還有".(3-$error_times)."次機會,可以重試!"));
$sql="update user set error_times=0 where uid='".$usrid."'";
mysql_query($sql,$conn);
$_SESSION['uid']=$usrid;
$_SESSION['gid']=$gid;
$sql="select * from user_info where uid='".$usrid."'";
$res2=mysql_query($sql,$conn);
$rs2=mysql_fetch_array($res2);
if (!($rs2==0)) {
    $_SESSION['uname']=$rs2['uname'];
    $_SESSION['ID']=$rs2['ID'];
    $_SESSION['birthday']=$rs2['birthday'];
    $_SESSION['city']=$rs2['city'];
    $_SESSION['address']=$rs2['address'];
    $_SESSION['email']=$rs2['email'];
    $_SESSION['dep']=$rs2['dep'];
    $_SESSION['school']=$rs2['school'];
    $_SESSION['grade']=$rs2['grade'];
    $_SESSION['class']=$rs2['class'];
}
if (intval($act)==1 || md5($usrid)==$inputpass)
    header("Location: /account/passin.php");
if (intval($act)==2)
    header("Location: /account/empudform.php?uid=".$usrid);
$topage=$_POST['jumpto'];
if (empty($topage))
    $topage="/";
if (intval($act)==0)
    header("Location: ".$topage);
?>

6.測試程式和偵錯

程式開發完畢之後,應該先進行程式測試,再上線運作。程式測試的目的主要是了解:

程式的測試最好是交由實際負責業務的人員來進行,而非由程式開發者自行測試,程式開發者因為早已熟知程式的運作方式,在進行測試時經常會以符合程式邏輯的方式來操作,因此無法發現較細微的錯誤!

當程式被測試出不理想的情況時,就需要進行程式的偵錯與改寫。所謂偵錯,就是要發現程式錯誤的地方,一般說來,程式的錯誤有下列幾種:

底下就常用的偵錯技巧逐一說明:

  1. 對於 SQL 語法錯誤的部分,我們可印出 SQL 字串來判斷。

  2. 如果是程式運算中的變數值有錯,我們可以秀出來檢查。例如:
    <script>alert('<?=$stdname?>')</script>

  3. 需要模擬學生的帳號權限,我們可以〝小心地〞考慮使用一組萬用密碼作為開發程式測試用。實作上,可以修改 check2.asp 內容讓程式判斷密碼只要是特定字串,檢查就算通過。例如:

    .....略.......
        if ($inputpass=="54_Student") {
            $_SESSION['passed']=1;
            ......................
        }
    .....略.......
  4. 使用萬用密碼時要特別注意:
    選用文數字混用的無規則可循的密碼,不容易被猜到
    此事需嚴格保密
    使用後要清除IE的密碼記憶功能
    限制網芳讀取該資料夾的權限

 

附錄A

物件導向設計

PHP 從 5.0 開始對於物件導向設計有完整的支援,使用物件導向設計必須先設計類別(class),所謂類別可以看成是物件的藍圖:

Class User { //宣告類別,名稱為 User
      Private $name; //此類別包含成員變數 $name
      Private $password; //此類別包含成員變數 $password
 
      Public function __construct($name) { //物件的建構函式
            $db=mysql_connect('127.0.0.1','root','test');
            $rs=mysql_query($db,"select * from users where id='$name'");
            $data=mysql_fetch_array($rs);
            $this->name = $data['name']; //將資料庫裡的資料指派給成員變數
            $this->password = $data['password'];
      }
 
      Function changePassword($oldpass,$newpass) { //類別的成員方法(method)
            if ($oldpass!=$this->password) die;
            $this->password = $newpass;
      }
}

利用繼承語法,我們可以把基礎類別發展成功能更強大的衍生類別,而不需要重新撰寫所有的程式碼:

Class Admin extends User { //Admin 繼承自 User
      Private $priv; //新增成員變數 $priv
 
      Public function __construct($name,$priv) { //Admin 物件的建構函式
           parent::__construct($name); //呼叫父類別的建構函式
           $this->priv=$priv;
      }
 
      Function checkPriv($app) { //新增加的成員方法
           $db=mysql_connect('127.0.0.1','root','test');
           $rs=mysql_query($db,"select * from priv where id='$this->priv' and app='$app'");
           $data=mysql_fetch_array($rs);
           return $data['priv_code'];
      }
}
介面用來約定一組虛擬方法,而實作此介面的物件都必須提供相對應的程式碼,運用這個方式,可以將不同物件集收於陣列中,透過迴圈來觸發虛擬方法,使不同物件做出相同的動作:
Interface form { //宣告介面,用來處理網頁表單
      abstract function show(); //虛擬方法,用來顯示表單
      abstract function setMethod(var $method); //虛擬方法,用來設定表單的傳輸方式
      abstract function setAction(var $action); //虛擬方法,用來設定表單資料的接收者
}
 
Class loginForm implements form { //要求登入表單類別,實作出表單介面
      ………………………………….
}
 
Class searchForm implements form { //要求搜尋表單類別,實作出表單介面
      ………………………………….
}
 
for (var i=0; i<count($thispage->forms);i++)
     $thispage->forms[i]->show(); //將網頁上所有表單都顯示出來

MVC 設計

前面介紹的物件導向是一種程式設計方法,而 MVC 則是一種設計模式(沒有固定的方法)
目前 PHP 上實作出來的 MVC 套件非常多,在此只介紹最常運用的 Smarty 模板。使用 Smarty 模板必須先設計網頁模板(View),習慣上會將它儲存成 *.tmpl:
smartytest.tmpl
<HTML>
<head>
<title>{$title}</title>
</head>
<body>
This is my template!
The value of the variable is: {$test_variable}
</body>
</HTML>

有了網頁模版後,必須要設計一個輸出入控制程式(Control),這個程式的功能是導入運算模組程式(Module),將模組程式處理過的資料,放置到網頁模版(View)中,再輸出給瀏覽器:

<?
Require('smarty.class.php'); //導入 module
 
$objSmarty = new Smarty; //建立一個新的 Smarty 物件
$template = 'smartytest.tpl';
 
$objSmarty->assign("title","Smarty test"); //將字串代入到模版中
$objSmarty->assign("test_variable", $data); //將 module 處理過的資料代入到模版中
 
$objSmarty->display($template); //讀取模板,進行巨集代換,然後輸出成網頁
?>

如果想要把資料輸出成表格,必須將資料裝在陣列中:

$objSmarty->assign("NAME", array("John","Mary","Jack"));

當然在網頁模版中,也必須先把表格設計好:

<table>
{section name=x loop=$NAME}
    <tr><td>{$NAME[x]}</td></tr>
{/section}
</table>

如果要處理的陣列,並非使用數字做為索引,而是使用字串,那麼可以寫成:

$arHash["Name"]="ED";
$arHash["Age"]=22;
$objSmarty->assign("writer",$arHash);

網頁模板的設計也必須配合修改為:

The author is {$writer.Name}, {$writer.Age} years old.

有時候會因應程式的運算結果來決定是否要顯示訊息,因此在網頁模板的設計上,允許使用 if 敘述來標示有可能不顯示出來的頁面內容:

{if $error_text}
The following error occurred: {$error_text}
{/if}

要不要顯示 if 範圍內的網頁內容,交由輸出入控制程式來決定:

$objSmarty->assign("error_text","Database not ready!"); //有指派資料給 error_text 則會輸出網頁內容,沒有指派時不輸出!

RIA 程式設計

前面已經介紹過 RIA 程式設計的概念,目前有兩種 RIA 可以選擇,包括:flash-base 和 javascript-base。兩者之間的差異比較如下:

Flash-base Javascript-base
流覽器需安裝外掛程式
支援影音串流
支援手機、PDA……等手持式設備
畫面可刷新重載,視覺效果好
學習難度較高
不需安裝外掛程式
處理影音串流須仰賴其他外掛,因此無法同步處理
不支援手持式設備
畫面無法刷新重載,容易造成使用者混淆
簡單易學

在 flash 中要存取資料庫,必須透過 php 程式來進行。目前已經有一套稱為 AMF 的 PHP 函式庫,直接安裝於 Apache 伺服器後,就可以直接使用 Action Script 來存取資料,不需要開發額外的 PHP 應用程式。如果要在未安裝 AMF 函式庫的情況下,傳遞資料給 flash 程式,必須自行開發 PHP 中介程式:

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<?
session_start();
include("../include/database.inc.php");

$sql=“select object_id from bag where uid=‘$_POST['uid']‘ ";
$res=mysql_query($sql,$conn);
$no=0; $bag='';
while($rs=mysql_fetch_array($res)) {
    $no++;
    $bag.=$rs[0].’,’;
}
$bag=substr($bag,0,strlen($bag)-1);
?>
&bag=<?=$no?>,<?=$bag?>

有了中介程式以後,flash 只要使用底下的程式碼就可以呼叫該程式取得資料庫的內容:

var ok = new LoadVars();
ok.uid = "shane";
ok.sendAndLoad("get_bag.php", ok, "POST");
ok.onLoad = function (succ) {
       if (succ) {
              bag=String(ok.bag).split(",");
              for (i=1;i<=bag[1];i++) {
                  trace(bag[i+1]) //依序列出資料庫的內容
              }
       }
}

看過 Flash 的 RIA 方法後,再來看 Ajax 如何使用 JavaScript 來做同一件事。要讓 JavaScript 呼叫 PHP 程式來取得資料庫內容,可以透過 XMLHttpRequest 物件來進行,這個物件是由瀏覽器提供的。JavaScript 最大的問題是來自不同的瀏覽器、不同的作業系統、不同的 MSoffice 版本,對這個技術的支持語法也不同。這些不同的創建方法有:

xmlhttp_request = new ActiveXObject("Msxml2.XMLHTTP.6.0");
xmlhttp_request = new ActiveXObject("Msxml2.XMLHTTP.4.0");
xmlhttp_request = new ActiveXObject("Msxml2.XMLHTTP.3.0");
xmlhttp_request = new ActiveXObject("Msxml2.XMLHTTP.2.6");
xmlhttp_request = new ActiveXObject("Msxml2.XMLHTTP.1.0");
xmlhttp_request = new ActiveXObject("Msxml2.XMLHTTP");
xmlhttp_request = new ActiveXObject("Microsoft.XMLHTTP");
xmlhttp_request = new XMLHttpRequest();

利用 XMLHttpRequest 物件呼叫 PHP 程式的方法如下:

Var A=new ActiveXObject("Msxml2.XMLHTTP");
A.open("POST","get_bag.php",true);
A.onreadystatechange = function() {
    if (A.readyState != 4) return;
    bag_list = A.responseText;
    bag=bag_list.split(",");
    for (var i=0;i<bag.length;i++)
        document.write(bag[i]."<br>");
}
A.send("uid=shane");
delete A;

後記

PHP 是個擴充性很強的工具,幾乎支援了所有現行的程式設計方法,能為你的網站帶來豐富的動態網頁內容。它所使用的語法感覺起來不陌生,而且也不會佔用多大的電腦資源 -- 還有你也不必為必須注意很多繁瑣的細節而感到沮喪,你所需要的只是寫簡單的描述語言。

PHP 還可以讓你從各式各樣的資料庫擷取以及儲存資料,包括: Postgres、MySQL、Oracle、Microsoft SQL Server,還有很多其他資料庫軟體。你可以隨時製作以及修改影像。你可以使用 Regular expression 操控文字資料。 你可以用 IMAP、LDAP、PDF、SNMP、WDDX、XML 等等函式庫,來改善你的程式。

想知道有關 PHP 的最新資訊,你可以到它的官方網站。另外,其線上使用手冊則是用來查詢如何使用某些函數好地方,事實上,連手冊都是開放原始碼的。