內(nèi)容提要 [隱藏] 由于學(xué)校社團(tuán)活動(dòng)需要一個(gè)支持?jǐn)?shù)據(jù)庫和用戶提交信息的動(dòng)態(tài)網(wǎng)站,所以花了幾個(gè)晚上速成了一下 PHP 和 MySQL 的相關(guān)知識(shí),之后就寫了一個(gè)網(wǎng)站,結(jié)果表明運(yùn)行的很順利。之前感覺這種動(dòng)態(tài)網(wǎng)站很深?yuàn)W,以為會(huì)很難,可是沒想到如果只實(shí)現(xiàn)不太復(fù)雜的功能的話,其實(shí)并不很難。 我爭取用最淺顯易懂的語言來說明一下是如何實(shí)現(xiàn)的。閱讀本文需要簡單的 HTML 基礎(chǔ)知識(shí)和(任一編程語言的)編程基礎(chǔ)知識(shí)(例如變量、值、循環(huán)、語句塊的概念等)。 PHP 基礎(chǔ)概述PHP 是一種解釋性語言,可用于對網(wǎng)頁進(jìn)行預(yù)處理。PHP 腳本在服務(wù)器端運(yùn)行,其運(yùn)行結(jié)果是一個(gè)可用來顯示的網(wǎng)頁。盡管可以完成許多類似工作,但是 Javascript 和 PHP 的一大區(qū)別就是,Javascript 是在瀏覽器端運(yùn)行的。事實(shí)上,瀏覽器會(huì)接收 Javascript 代碼,并運(yùn)行它,所以理論上用戶是可以檢查 Javascript 代碼的。而 PHP 不會(huì)將原始代碼交給瀏覽器, 只會(huì)將其運(yùn)行的結(jié)果交給瀏覽器,所以用 PHP 處理用戶登陸、用戶權(quán)限等問題是安全可靠的。 PHP 與 HTML實(shí)際編寫的時(shí)候,通常采用的方式是建立擴(kuò)展名為 規(guī)則:php 代碼需要包含在 <?php // code goes here ?> 提示:這是一個(gè) php 和 html 混編的較為生動(dòng)的例子。 <?php if ($var == "true") { ?> <html id="ie6"> <?php } else { ?> <html id="ie8"> <?php } ?> 這里的意思是,如果 php 中的變量 關(guān)于 PHP 中的操作符PHP 采用的操作符和 C/C++ 是類似的,例如用 關(guān)于 PHP 中的變量PHP 中變量的命名一律以符號(hào) 關(guān)于 PHP 中的語句這一點(diǎn) PHP 和許多其他常見的編程語言很類似,也可以用 MySQL 基礎(chǔ)使用 MySQL 數(shù)據(jù)庫是存儲(chǔ)數(shù)據(jù)的一種方法,MySQL 需要和 PHP 配合來完成對數(shù)據(jù)庫的查詢(這里術(shù)語“查詢”包括寫入、更新、讀取等)操作。利用 MySQL,你可以創(chuàng)建許多數(shù)據(jù)庫(database),每個(gè)數(shù)據(jù)庫可以包含多個(gè)表(table),而每個(gè)表包含若干字段。為了高效,一般會(huì)采取分類維護(hù)多個(gè)表的方式,而不是把所有數(shù)據(jù)都儲(chǔ)存在同一個(gè)表中。 MySQL 需要服務(wù)器支持。使用的第一步是建立一個(gè)數(shù)據(jù)庫,可以用相應(yīng)的圖形化工具(例如 phpMyAdmin)來建立數(shù)據(jù)庫,也可以在終端直接使用下列 SQL 語句來創(chuàng)建一個(gè)名為 database_name 的數(shù)據(jù)庫: CREATE DATABASE database_name; 創(chuàng)建好數(shù)據(jù)庫之后,需要?jiǎng)?chuàng)建表??梢酝ㄟ^下列 SQL 語句來創(chuàng)建一個(gè)名為 table_name 的表: USE database_name; CREATE TABLE table_name ( first_name varchar(30), last_name varchar(30), ); 第一句說明在哪個(gè)數(shù)據(jù)庫中添加表,第二個(gè)說明添加的表的詳情。這里我們在表中添加了兩個(gè)字段,分別叫做 其他常見的數(shù)據(jù)類型如下: VARCHAR(100) --可變字符 CHARACTER(1) --定長 INTEGER --整數(shù) DECIMAL(10, 2) --小數(shù)(小數(shù)點(diǎn)前后的位數(shù)) TIMESTAMP --日期和時(shí)間 DATE --日期 你可能已經(jīng)看出來了,MySQL 的注釋符為 使 PHP 和 MySQL 協(xié)作第一種方式現(xiàn)在你已經(jīng)創(chuàng)建好了 SQL 數(shù)據(jù)表,并對 PHP 語言有了一個(gè)概覽。下面我們直奔主題,學(xué)習(xí)如何對數(shù)據(jù)表進(jìn)行查詢。 為了使 PHP 和 MySQL 進(jìn)行交互,需要為 PHP 提供你的數(shù)據(jù)庫用戶名、密碼以及數(shù)據(jù)庫名、數(shù)據(jù)表名,當(dāng)然,最重要的,查詢操作的 SQL 語句。我們一一來觀察是如何實(shí)現(xiàn)的。 <?php define('DB_HOST', 'localhost'); define('DB_USER', 'renfei'); define('DB_PASS', 'root'); define('DB_NAME', 'database_name'); $dbc = mysqli_connect(DB_HOST, DB_USER, DB_PASS, DB_NAME); $query = "INSERT INTO table_name (column_name1, column_name2) VALUES ('value1', 'value2')"; mysqli_query($dbc, $query); ?> 下面來解釋一下這一坨代碼的工作原理。
事實(shí)上,如果把這些代碼保存成一個(gè)網(wǎng)頁,當(dāng)用戶打開網(wǎng)頁的時(shí)候,如果各項(xiàng)參數(shù)正確,它就會(huì)完整地運(yùn)行下去。 這里的 SQL 語句的含義是向叫做 INSERT INTO table_name (column_name1, column_name2, ...) VALUES ('value1', 'value2', ...) 如果你要做的僅僅是執(zhí)行一個(gè) SQL 語句,那么使用這種模式就可以完成。提醒一下, 另一個(gè)常用的 SQL 語句就是修改某一行。它的形式為: UPDATE table_name SET column_name1='preferred_value1', column_name2='preferred_value2', ..., WHERE id = '$id' 當(dāng)然,這個(gè)語句應(yīng)該是寫到一行的,不過為了清晰我分開來寫。它的含義是,修改名為 注意:會(huì)修改所有符合 WHERE id = '$id' AND is_male = 'true' WHERE id = '$id' OR pid = '$id' 都是合法的(如果你想問 下面介紹其他 SQL 語句。 --更新table_name表中的數(shù)據(jù) UPDATE --刪除table_name表中的所有行 DELETE FROM table_name --刪除table_name表中email字段為a@do.com的所有行 DELETE FROM table_name WHERE email = 'a@do.com' --刪除table_name表 DROP TABLE table_name --刪除table_name表中的score字段 ALTER TABLE table_name DROP COLUMN score --給table_name表添加一個(gè)叫age的字段,類型為 INTEGER ALTER TABLE table_name ADD COLUMN age INTEGER --把table_name表中的col字段改名為loc,并設(shè)類型為 INTEGER ALTER TABLE table_name CHANGE COLUMN col loc INTEGER 可見,第一種方式的本質(zhì)就是編寫一條 SQL 語句,然后通過 PHP 來執(zhí)行它。下面,我們來看第二種方式。 第二種方式有時(shí),我們不滿足于讓服務(wù)器去執(zhí)行一條 SQL 語句。我們會(huì)需要從數(shù)據(jù)庫中查詢信息,然后把得到的信息儲(chǔ)存起來(其實(shí)就是儲(chǔ)存在變量中)。這樣,我們需要一些額外的工作。先看一坨代碼: <?php $dbc = mysqli_connect(DB_HOST, DB_USER, DB_PASS, DB_NAME); $query = "SELECT * FROM table_name WHERE problem_id='$id'"; $result = mysqli_query($dbc, $query); $row = mysqli_fetch_array($result); $problem_title = $row['problem_title']; ?> 這里我們省略了 這一坨代碼和上一坨的主要區(qū)別是,我們使用了 解釋一下 然后,用變量 到這里你應(yīng)該問一個(gè)問題:如果滿足 最后補(bǔ)充一點(diǎn)剛才沒有提到的。如果不需要所有字段的數(shù)據(jù),可以只選擇需要的字段。方法是把原來 SQL 語句中的通配符換成字段名稱。例如: SELECT problem_name, problem_type FROM table_name WHERE problem_id='$id' while 循環(huán)在 PHP 中的應(yīng)用舉例如果我們要把一個(gè)數(shù)據(jù)庫的許多行信息都展示在網(wǎng)頁中,那么需要用到 <?php $dbc = mysqli_connect(DB_HOST, DB_USER, DB_PASS, DB_NAME); $query = "SELECT user_id FROM database_name ORDER BY user_id ASC"; $result = mysqli_query($dbc, $query); while ($row = mysqli_fetch_array($result)) { $user_id = $row['user_id']; echo "<tr>"; echo "<td>".$user_id."</td>"; echo "</tr>"; } ?> 如果有一定編程基礎(chǔ)的話上面的代碼很容易看懂。上面新出現(xiàn)了三種用法,說明如下:
SELECT user_id, user_rank FROM table_name ORDER BY user_rank DESC, user_id ASC
從表單獲取信息概述這一部分我們演示如何構(gòu)建一個(gè)表單,使用戶填寫這個(gè)表單并把內(nèi)容儲(chǔ)存到數(shù)據(jù)庫。這一技術(shù)是用戶注冊系統(tǒng)和用戶互動(dòng)的基礎(chǔ)。 要實(shí)現(xiàn)這個(gè)功能,需要 HTML 和 PHP 配合完成。HTML 負(fù)責(zé)表單,而 PHP 負(fù)責(zé)獲取信息并使用 SQL 查詢儲(chǔ)存信息。首先來看 HTML 部分(就是普通的表單): <form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>"> <label for="username">用戶名:</label> <input type="text" id="username" name="username" /><br/> <label for="info">信息:</label> <input type="text" id="info" name="info" /><br/> <input type="submit" value="Submit" name="submit"/> </form> 屬于 HTML 部分的不再解釋了,說一說新鮮的。這里的 action 屬性后面的 下面來看一下相應(yīng)的 PHP 處理部分的代碼: <?php $dbc = mysqli_connect(DB_HOST, DB_USER, DB_PASS, DB_NAME); if (isset($_POST['submit'])) { $user = $_POST['username']; $info = $_POST['info']; $query = "INSERT INTO table_name (tb_user, tb_info) VALUES ('$user', '$info')"; mysqli_query($dbc, $query); echo "<p>提交成功</p>"; } mysqli_close($dbc); ?> 首先仍然是建立數(shù)據(jù)庫連接。當(dāng)用戶點(diǎn)擊 sumbit 按鈕后,表單的內(nèi)容會(huì)被儲(chǔ)存在 PHP 中 這里新出現(xiàn)了一個(gè)內(nèi)容,就是 需要注意的是,這僅僅是最簡單的代碼,而且實(shí)際上是不完善的。如果要真正投入使用,我們需要使它更健壯一些。下面逐一討論這些內(nèi)容。 檢查用戶輸入是否合法如果用戶根本沒有填寫表單,就直接點(diǎn)擊提交按鈕,會(huì)發(fā)生什么?在上面的實(shí)例中,PHP 依然會(huì)乖乖地把空內(nèi)容插入,而這顯然是垃圾信息,不是我們需要的。所以,需要在插入前檢查被插入的變量是否為空。例如: <?php if (!empty($user) && !empty($info)) { // 插入操作 } ?> 這里出現(xiàn)了 錯(cuò)誤提示用戶輸入有誤時(shí),上面的改進(jìn)除了不執(zhí)行SQL查詢,并沒有多少直觀上的變化。用戶不會(huì)收到任何信息表明他們的填寫是不合適的。所以我們要在這時(shí)產(chǎn)生一些提示,引導(dǎo)用戶正確填寫表單。 <?php if (!empty($user) && !empty($info)) { // 插入操作 } else { echo "請?zhí)顚懭績?nèi)容后再提交"; } ?> 防范 SQL 注入攻擊我們執(zhí)行的 SQL語句中包含變量,執(zhí)行的時(shí)候會(huì)直接把變量內(nèi)容替換進(jìn)去。而如果攻擊者在輸入框中輸入一些危險(xiǎn)的字符(通常包含 SQL 注釋符 <?php $user = mysqli_real_escape_string($dbc, trim($_POST['username'])); $info = mysqli_real_escape_string($dbc, trim($_POST['info'])); ?> 粘性表單如果用戶第一次填寫失敗,他們希望能保留已經(jīng)填寫好的內(nèi)容,只做些修改就好了。這需要使用粘性表單技術(shù)。要實(shí)現(xiàn),只需要稍稍改動(dòng) HTML 表單部分的代碼: <label for="username">用戶名:</label> <input type="text" id="username" name="user" value="<?php if (!empty($user)) echo $user; ?>" /> 顯而易見,如果用戶填寫后因?yàn)槟承┰驔]有提交而是回到了這個(gè)表單,并且之前填寫了 user 字段的內(nèi)容,那么此時(shí) 構(gòu)造一個(gè)注冊頁面雖然上面說了很多,但是僅僅滿足了我們最基本的輸入要求。許多時(shí)候我們需要更為復(fù)雜的功能。舉例來說,要寫一個(gè)注冊頁面,必須檢查用戶名是否重復(fù),還要對密碼采取某種技術(shù)加密以保證安全。 檢查用戶是否重復(fù)基本原理就是,根據(jù)需要判重的字段(例如用戶名)去數(shù)據(jù)庫搜索。如果發(fā)現(xiàn)結(jié)果則用戶名重復(fù),如果沒有找到則允許注冊。需要一個(gè)新函數(shù) <?php $dbc = mysqli_connect(DB_HOST, DB_USER, DB_PASS, DB_NAME); $query = "SELECT * FROM table_name WHERE user_name = '$user'"; $data = mysqli_query($dbc, $query); if (mysqli_num_rows($data) == 0) { // 把內(nèi)容插入數(shù)據(jù)庫 echo "注冊成功"; mysqli_close($dbc); exit(); } else { echo "用戶名已被占用,請重新選擇用戶名"; $user = ""; } ?> 把 需要說明的是 對密碼進(jìn)行加密存儲(chǔ)明文存儲(chǔ)密碼是對用戶很不負(fù)責(zé)的,不僅數(shù)據(jù)庫管理員可以看到密碼,一旦數(shù)據(jù)庫泄漏,密碼就會(huì)被公開。所以,我們應(yīng)該加密存儲(chǔ)用戶密碼。在 PHP 中,可以使用 加密的原理是,用戶輸入密碼后,利用 PHP 把 hash 過的密碼儲(chǔ)存在數(shù)據(jù)庫中。用戶登陸的時(shí)候,把用戶輸入的密碼進(jìn)行 hash 運(yùn)算,之后和數(shù)據(jù)庫中的進(jìn)行比對。 使用方法如下: SHA('$password') 其他要素對于注冊頁面,不要忘記確認(rèn)密碼,即讓用戶輸入兩次,比較確定相等后再執(zhí)行注冊。 識(shí)別用戶登錄:Cookie只注冊沒有用,必須添加登錄功能。登錄功能可以使用 Cookie 來實(shí)現(xiàn)。這里假定你已經(jīng)了解 Cookie 的基礎(chǔ)知識(shí),只說如何實(shí)現(xiàn)。 設(shè)置 Cookie<?php setcookie('user_type', $user_type, time() + (60 * 60 * 24 * 30)); ?> 上面的代碼用來設(shè)置 Cookie,其中函數(shù)的第一個(gè)參數(shù)為 Cookie 名稱,第二個(gè)參數(shù)為數(shù)值(這里用一個(gè)變量傳遞),第三個(gè)參數(shù)為過期時(shí)間,單位秒。示例為一個(gè)月。 可以用設(shè)置多個(gè) Cookie 來存儲(chǔ)許多內(nèi)容,例如用戶 ID、用戶組(管理員還是普通用戶)等。 驗(yàn)證 Cookie用戶登陸后,我們可以設(shè)置一個(gè) Cookie 來存儲(chǔ)登錄信息(即哪個(gè)用戶登陸的),然后通過檢查這個(gè) Cookie 來設(shè)定相應(yīng)功能。 <?php if (isset($_COOKIE['user_type'])) { $user_type = $_COOKIE['user_type']; } ?> 刪除 Cookie要?jiǎng)h除 Cookie,只需要把過期時(shí)間設(shè)定在過去。 <?php setcookie('user_type', '', time() - 3600); ?> 不要問我為什么設(shè)定在過去一個(gè)小時(shí),設(shè)定幾個(gè)小時(shí)都沒問題。 Cookie 的安全性設(shè)置 Cookie 有其潛在的危險(xiǎn)。由于 Cookie 是保存在用戶本地的,所以用戶完全可以通過篡改 Cookie 來達(dá)到他們的目的。所以,把 Cookie 的值設(shè)置得“通俗易懂”不是一個(gè)好主意。例如,我們要用 Cookie 來保存登陸的用戶名,如果單純把這個(gè)用戶名存入 Cookie,那么攻擊者會(huì)很容易通過修改成他人的用戶名來偽造 Cookie 登陸。所以,我們需要其他的手段來防止這一點(diǎn)。 我的做法是,用戶注冊的時(shí)候,把用戶名按一定手段進(jìn)行變換,然后使用 使用 GET 方法在網(wǎng)頁間傳遞信息除了剛才介紹的 http://www./index.php?id=2 網(wǎng)址最后有 <?php if (isset($_GET['id'])) { $id = $_GET['id']; // code goes there } ?> 這個(gè)例子中我們把 http://www./index.php?id=2&message=10 除了多一個(gè)可以使用的 這個(gè)特性的用處之一就是可以根據(jù)網(wǎng)址的不同,配合數(shù)據(jù)庫查詢,返回不同的網(wǎng)頁內(nèi)容。例如我做的在線問答系統(tǒng),就是根據(jù) 注意,由于 另外,如果你的表單是用來上傳文件的,那么估計(jì)你會(huì)更喜歡 POST 和 GET 方法混用設(shè)計(jì)較為復(fù)雜的頁面的時(shí)候,我們往往需要在一個(gè)頁面內(nèi)同時(shí)處理 POST 和 GET 方法的數(shù)據(jù)。例如一個(gè)答題頁面,需要 GET 方法來獲取題目信息,同時(shí)需要 POST 方法來把用戶的回答儲(chǔ)存到數(shù)據(jù)庫中。同時(shí),這個(gè)頁面可能還需要針對不同的回答給出不同的反饋。 要處理這個(gè)問題,只需要理解并用好 isset() 函數(shù)即可。例如,通??梢赃@樣組織頁面: <?php if (isset($_GET['id'])) { $id = $_GET['id']; } else if (isset($_POST['id'])) { $id = $_POST['id']; } else { echo "fatal error"; } // use $id to fetch problem information // and display the problem if (isset($_POST['submit'])) { // check and process } ?> 使用模板最后一部分,來講一下使用模板構(gòu)造一個(gè)網(wǎng)站。 事實(shí)上,網(wǎng)站的每個(gè)頁面中,有許多部分是完全相同的,例如每一頁的 header 和 footer 部分。這樣,我們沒必要在每一頁內(nèi)寫相同的代碼。除了麻煩和浪費(fèi)空間以外,還有一點(diǎn)很重要的原因,就是修改的時(shí)候工作量很大。 PHP 中 <?php require_once('define.php'); ?> 會(huì)把 PHP的錯(cuò)誤處理分級的錯(cuò)誤信息最后來講一下 PHP 的錯(cuò)誤處理機(jī)制。如果你寫了有錯(cuò)誤的 PHP 代碼,那么運(yùn)行的時(shí)候系統(tǒng)會(huì)自動(dòng)生成一些錯(cuò)誤提示信息并且打印到屏幕上,以提醒用戶修復(fù)。通常,這些錯(cuò)誤信息是分級的。首先,是 在寫 PHP 程序的時(shí)候,我們需要這些錯(cuò)誤提示來幫助我們改正錯(cuò)誤,但是當(dāng)產(chǎn)品發(fā)布的時(shí)候,開發(fā)人員往往傾向于隱藏錯(cuò)誤提示:用戶收到這些信息是很讓人惱火的,而且,讓他人知道你的代碼有什么漏洞總歸不是一個(gè)好主意,因?yàn)檫@可能被某些圖謀不軌的攻擊者加以利用。 Suppression Operator有時(shí),為了代碼的簡潔性考慮我們可能會(huì)故意犯一些無關(guān)痛癢的小錯(cuò)誤。例如,如果 <?php if (@$_GET['opt']) { // code goes here... } ?> 其他提示
|
|