# Back-End 基礎(認識 PHP 與 MySQL) ###### tags: `Lidemy` 以下為 Lidemy [MTR05] 課程筆記,如有錯誤,歡迎留言/寄信通知,感謝。 ## 環境建置 - 本小筆記使用的工具為 [XAMPP](https://www.apachefriends.org/index.html)(Apache + MariaDB + PHP + Perl) 下載後打開 panel,將 Apache 及 MySQL 啟動 ## PHP 記得要把程式碼包在 php 裡面: ``` <?php 你的程式碼 ?> ``` 每一行的結尾都要加上 ";" 變數的表示:$ ``` $a = 1; echo $a; ``` 字串拼接用 ".",否則會 error,"+"僅限於數字運算 ``` EX1 $a =dsadad; $b =sadksapod; echo $a . $b EX2 // 注意,這邊無法像 js 用 + "\n" for ($i=0; $i<5; $i++) { echo $i . "<br>" } ``` 在 $arr 下,無法使用 echo,因此需要使用: 1. `var_dump()` 另類 echo,但會把變數的 type 跟 value 都印出 2. `print_r()`,不會顯示 type --- 使用 php 存取資料 `$_GET` 取得 GET 的資料 `isset` 檢查是否有傳 key,空 value 的話依舊是 true `empty` 檢查是否為空值 例子: ### conn.php (存放資料庫資訊) ```php= <?php $server_name = 'localhost'; $username = 'xxx'; $passowrd = 'xxx'; $db_name = 'xxx'; $conn = new mysqli ($server_name, $username, $passowrd, $db_name); if ($conn->connect_error) { die('error:' . $conn->connect_error); } $conn->query('SET NAMES UTF8'); $conn->query('SET time_zone = "+8:00"'); ?> ``` ### index.php (主要顯示頁面) ```php= <?php // 若使用 PHP 內建 session 請加上: session_start(); require_once('conn.php'); $result = $conn->query("select * from XXX"); if (!$result) { die($conn->error); } while ($row = $result->fetch_assoc()) { echo "id" . $row['id'] . '<br>'; echo ... // 在 html 中可以這樣穿插,但僅選於單一值 /* <?=$row['id'] ?>; */ }; if(!empty($_GET['errCode'])) { $code = $_GET['errCode']; $msg = 'ERROR...'; if ($code === '1') { $msg = 'E.X. 請輸入完整資料'; } echo '<h2>' . $msg . '</h2>'; } // 選擇一:PHP 內建 Session(token),內建與自建 // {下方有範例...} // 選擇二:自建 token,從 tokensTable 中找尋其相對應的 username // {下方有範例...} // 內建與自建,只需選一個來用即可 ?> <h2>新增 Example</h2> <form method="POST" action="add.php"> username: <input name="username" /> <input type="submit" /> </form> ``` > 若使用 PHP 內建的 Session (cookie): ```php= /* PHP 實際在跑的程序: 1. 從 cookie 裡讀取 PHPSESSID(token),這東西是在 login_handle.php 產生的 2. 從檔案裡讀取 session id 的內容 3. 放到 $_SESSION */ $token = NULL; if(!empty($_SESSION['tokenName'])) { $token = $_SESSION['tokenname']; } ``` > 若使用自建的 cookie: ```php= // 去 DB 找 token table $token = NULL; if(!empty($_COOKIE['token'])) { $token = $_COOKIE['token']; $sql = sprint( "select username from tokenTable where token = '%s'", $token ); $token_reuslt = $conn->query($sql); $row = $token_result->fetch_assoc(); $username = $row['username']; } ``` ### add.php (新增時會執行的 action ) ```php= <?php require_once('conn.php'); if (empty($_POST['xxx'])) { header('Location: index.php?errCode=1'); die('請輸入 xxx'); } // 情況 1:單純新增 $xxx = $_POST['xxx']; $sql = sprintf( "INSERT INTO tableName(tableColumn) VALUES('%s', '%d')", $STRING, $NUM ); $result = $conn->query($sql); if (!$result) { die($conn->error); } // 情況2:內建 Session(cookie) // 情況3:自建 cookie // 加入使用者的暱稱(使用者只要輸入內容,其使用者暱稱自動帶入) $nikcname = $row['nickname']; $content = $_POST['content']; $sql = sprintf( "insert into comments(nickname, content) values('%s','%s')", $nickname, $content ) // 執行完後返回 index.php hander("Location: index.php"); ?> ``` > 若使用 PHP 內建的 Session (cookie): ```php= // 記得要先加上 session_start(); $user = $_SESSION['token']; $user_sql = sprintf( "select * from users where username = '%s'", $token ); $user_result = $conn->query($uer_sql); $row = $result->fetch_assoc(); ``` > 若自行建立 cookie : ```php= $username = $_COOKIE['cookieName']; $user_sql = sprintf( "select nickname from tableName where username='%s' ", $username ); $user_result = $conn->query($user_sql); $row = $user_result->fetch_assoc(); ``` ### login_handle.php 加上 cookie (登入後的處理,讓 HTTP 記得你就是那位使用者) ```php= <?php require_once('conn.php'); if (empty($_POST['password']) || empty($_POST['username']) ) { header('Location: login.php?errCode=1'); die(); } $username = $_POST['username']; $password = $_POST['password']; $sql = sprintf( "select * from users where username='%s' and password='%s'", $username, $password ); $result = $conn->query($sql); if(!$result) { die($conn->error); } if ($result->num_rows) { // 選擇內建或自行建立的 cookie // {...} header("Location: index.php"); } else { header("Location: login.php?errCode=2"); } ?> ``` > 若使用 PHP 內建的 Session (cookie): ```php= // 記得先加上 session_start(); /* if ($result->num_rows) {},中,加入下放的一句 code 實際跑了下列的步驟: 1. 產生 session id (token) 2. 將 sessionName 寫入檔案 3. set-cookie: session id 最後記得要在 index.php 也加入相對內建 session 的語法 */ $_SESSION['sessionName'] = $sessionName ``` > 若使用自建的 Cookie : ```php= $token = generateToken(); $sql = sprintf( "insert into token(token, username) values ('%s','%s')", $token, $username ); $result = $conn->query($sql); if(!$result) { die($conn->error); } // 登入成功 $expire = time() + 3600 * 24 * 30 // time()是現在的時間,不加的話會顯示為 1970,設了 ==== 沒設 setcookie("token", $token, $expire); ``` ### logout.php (把 cookie 刪掉) ```php= <?php require_once('conn.php'); // 使用 PHP 內建 token 的話: // {...} // 使用自建 token 的話: // {...} header("Location: index.php"); ?> ``` > 使用 PHP 內建的 token: ```php= // 或許你會問為何要 session_start ? 因為只要用到 session 的地方,就要先用 session_start(),來自官網的答案。 session_start(); session_destroy(); ``` > 使用 自行建立的 token: ```php= $token = $_COOKIE['token']; $sql = sprintf ( "delete from tokens where token='%s'", $token ); $conn->query($sql); setcookie("cookieName", "", time() - 3600); ``` ### delete.php 直接從 add.php 著手修改,通常 DEL 會用 post 的方式,但這邊簡單示範,因此使用 GET。 記得要在 index.php 加入 刪除 button `echo "<a href='delete.php?id=x" . $row['id'] . "'>刪除</a>"` ```php= <?php require_once('conn.php'); if (empty($_GET['xxx'])) { die('請輸入 xxx'); } $id = $_GET['id']; $sql = sprintf( "DELETE FROM tableName WHERE id = %d", $id ); echo $sql $result = $conn->query($sql); if (!$result) { die($conn->error); } // 用 affected_rows 查看實際影響了幾列 if ($conn->affected_rows >= 1) { echo '刪除成功'; } else { echo '查無資料'; } hander("Location: index.php"); ?> ``` ### update.php ```php= <?php require_once('conn.php'); if(empty($POST['id'] || empty($_POST['username']))) { die('請輸入 id 與 username'); } $id = $POST['id']; $username = $POST['username']; $sql = sprintf( "update users set username = '%s' where id = %d", $username, $id ); echo $sql . '<br>'; $result = $conn->query($sql); if(!result) { die($conn->error); } header("Location: index.php") ?> ``` ### utils.php(放一些共用的 function) 例如: ```php function generateToken() { $t = ''; for($i=0; $i<16; $i++) { $t .= chr(rand(65,90)); } return $t; } ``` > ### 當你以為開開心心,用上面各種的 php 完成想要的畫面就完成了,其實危機四伏,一堆資安問題未解決... 1. 密碼! 資料庫的密碼是明文密碼,這樣太危險,資料庫被駭,就被看光光 解決方法:內建 hash 函式 [password_hash()](https://www.php.net/manual/en/function.password-hash.php) 2. XSS (Cross-site Scripting) ~~不要問 XSS 為什麼不叫 CSS,因為 CSS 太出名了噗~~ 使用者可以在 input 的地方隨意使用不同的 tag,如 `<scipt>alert(123)</script>` 或 `<h1></h1>` 解決方法:內建 htmlspecialchars 函式 [htmlspecialchars](https://www.php.net/manual/en/function.htmlspecialchars.php) example: `htmlspecialchars($str, ENT_QUOTES);` 3. SQL Injection 駭客的填字遊戲,透過能 input 的地方,留下 sql query(防了`<script>`還得防 sql query 呀!) 解決方法:prepare statement,不要再用 springf + %s 了~ ```php= // 原本: $sql = sprintf( "INSERT INTO tableName(tableColumn1, tableColumn2) VALUES('%s', '%d')", $STRING, $NUM ); $result = $conn->query($sql); if (!$result) { die($conn->error); } // 更改為: $sql = "INSERT INTO tableName(tableColumn1, tableColumn2) VALUES(?, ?)"; $stmt = $conn->prepare($sql); // 兩個 s === 兩個 string 變數 $stmt->bind_param('ss', $nickname, $content); // 從 query($sql) 改為 execute() $result = $stmt->execute(); if (!$result) { die($conn->error); } // 取值 (若需要的話,譬如用 select ) $result = $stmt->get_result(); ``` ## 資料庫 server => 程式,專門處理 request 跟 response 的程式 資料庫系統 => 程式,專門處理資料的程式 種類(主要兩種): 1. 關聯式資料庫(Relational database) 較普遍,藉由 Table 來做關聯、分類 e.g. MySQL、PostgreSQL 2. NoSQL (Not only SQL) 通常搭配 log (日誌)使用,不用額外加 table name 才加入資料,可以隨意新增刪減,相對關聯式 DB 沒那麼結構化。 e.g. MongoDB --- MySQL 基礎語法 - 查詢 Select `SELECT * From tableName WHERE (條件) and/or (條件2)` - 新增 Insert `INSERT INTO tableName (tableColumns) VALUES (valueOfColumns)` - 修改 Update `UPDATE tableName SET tableColumn1 = 'newValue', tableCoumn2 = 'newValue' WHERE 條件` - 刪除 Delete `DELETE FROM tableName WHERE 條件` ## 來個小實作 - 留言板(待完成) 一堆堆理論,看了未必懂,來實作一下吧 ### 步驟一:規劃產品路由及功能 1. 觀看所有留言 -> index.php 2. 新增留言 -> handle_add.php ### 步驟二:檢視資料結構 1. id -> INT, AI (auto_increment), PK (primary key) 2. name -> VARCHAR(128) 3. content -> TEXT 4. created_at -> DATETIME -> CURRENT_TIME (用於記錄提交的時間) total: 4 columns #### 實作前端頁面 index.php