本篇筆記使用 XAMPP 來學習後端,會學到的有:
- PHP 基本語法
- PHP 背後運作原理簡單介紹
- phpmyadmin 資料庫介面管理 MySQL
- MySQL 語法基礎
實作練習:用 PHP 操作 MySQL 裡的資料
5.1 初探 PHP: PHP 裡有哪些函式?
5.2 PHP 連線到 MySQL 資料庫
5.3 讀取資料:Read
5.4 新增資料:Create / 包含動態新增
5.5 刪除資料:Delete 待續...
5.6 編輯資料:Update 待續...真正的實戰:留言板 - 初階實作篇
6.1 串接資料庫顯示留言
6.2 新增留言功能
6.3 實作註冊功能
6.4 實作登入功能
6.5 如何讓瀏覽器用 COOKIE 記錄登入狀態
6.6 自己實作通行證機制
6.7 PHP 內建 session 機制
備註:這是 Lidemy 線上課程 [ BE101 ] 的筆記
1. PHP 基本語法
- 一定要用標籤 <?php?> 包住,看下面範例
- 結尾都要加分號
- 變數前面要加 $
- 要印出 arr 或資料型態不是單純數字加字串時,用 var_dump or print_r
- 字串拼接是用 .
<?php
$a = 'foo'; // 宣告變數 a = foo
$b = 2;
echo $a; // 印出 變數 a
echo $a . $b; // 印出 a + b -> foo2
// if...else... 語法
$score = 60;
if ($score >= 60) {
echo 'pass';
} else {
echo 'fail';
}
// for loop
for ($i = 0; $i <= 10; $i++) {
echo $i;
echo $i . '<br>';
}
echo $i . '<br>'; // 會印出
0
1
2
3
4
5
6
7
8
9
10
$arr = array(1, 2, 3, 4, 5);
echo $arr[0];
// 會印出 1;特別注意括號用法
echo $arr[sizeof($arr) - 1];
// 會印出 5
// sizeof() 取得 arr 長度
echo $arr; // 印不出 arr 內容
var_dump($arr) // 比較詳細,會印出
array(5) { [0]=> int(1) [1]=> int(2) [2]=> int(3) [3]=> int(4) [4]=> int(5)
print_r($arr) // 不會有 type,會印出
Array ( [0] => 1 [1] => 2 [2] => 3 [3] => 4 [4] => 5 )
function add($a, $b) {
return $a + $b;
}
echo add(1, 2); // 會印出 3
?>
2. Apache 與 PHP 原理簡介
- 背後流程:
- request -> apache(server) -> php -> output -> apache -> response
資料庫系統簡介
- 什麼是資料庫?
- 專門來處理資料的一個程式
- 關聯式資料庫:MySQL, PostgreSQL 這兩個最有名
- 如果用 XAMPP 的話,裡面用的不是 MySQL 而是 MariaDB,當初 MySQL 被買走了,怕以後不開源,所以 MariaDB 是永遠開源,為了跟 MySQL 完全相容。
- 但是 MySQL 跟 MariaDB 使用上差不多
- 非關聯式資料庫:例如 MongoDB
3. 如何管理資料庫? phpmyadmin 簡介
phpmyadmin: 一個管理資料庫的介面
- 之前有過安全性的問題,不過功能完整
- 或者使用 Adminer 也是用 php 寫的,介面比較簡單一點
Index、Unique 這些有什麼用?
- Index 索引
- 之後要查詢資料比較快
- 像是目錄或標籤一樣
- 可以是組合的欄位,例如 username + password
- Unique
- Primary Key (PK 又稱主鍵)不能是空的且不能重複
- 或也可以設定 unique,可以防止寫入重複的資料
- Index 索引
4. MySQL 語法基礎
select 查詢資料: SELECT id FROM data
- SELECT id as name FROM data
- 改變欄位名稱叫做 name,但還是一樣是 id 內容
- SELECT id FROM data WHERE id = 2
- SELECT * FROM
data
WHERE username = 'yoyoyo' and id = 1,data 有沒有反引號都可以
- SELECT id as name FROM data
insert 新增資料:
- sql 指令永遠都大寫
- 查詢的資料可用反引號包起來或不包,但如果你的名稱是跟 sql 內建指令一樣的話要包,不然會搞混
INSERT INTO `data`(`id`, `username`, `content`, `created_at`) VALUES ('[value-1]','[value-2]','[value-3]','[value-4]')
- update 修改資料:
UPDATE `data` SET `id`='[value-1]',`username`='[value-2]',`content`='[value-3]',`created_at`='[value-4]' WHERE 1
UPDATE
data
SET username = qoo WHERE id = 1; 如果沒有 WHERE 後面的指定選項的話,就會更改所有的資料delete 刪除資料:
DELETE FROM data WHERE id = 1
補充說明:有時候會設定 is_deleted 的標籤(選boolean,只有 0 or 1)
- 如果前台選刪除,那就改成 1,這樣如果誤刪的話,資料可以救回來
- 如果真的用 delete 且沒有備份,那就救不回來了
- 或者拿來決定是否要呈現在使用者面前
5. 實作練習:用 PHP 操作 MySQL 裡的資料
5.1 初探 PHP
從前端傳資料給後端:GET 與 POST
- 這個影片要好好複習,這裡的語法都要會
- 主要有兩大部分:
- 利用網址上的 query string 拿資料
- 透過表單的 method 及 action
- 通常比較常用的有 GET 和 POST
影片裡的函式有:
- exit(): 類似 js 裡面 return 的概念
- isset():
- isset($_GET['name']) 如果有填寫 name 的內容
- empty(): 如果內容為空的話
0 - 4'30": 印出網址上 query string 的資料
// 例如網址是:http://localhost:8080/christy/test.php?a=1 這裡的 a 是 key, 1 是 value
執行 print_r($_GET)
會印出 Array ( [a] => 1 )
// 或者是 http://localhost:8080/christy/test.php?a=1&b=3
echo 'a:' . $_GET['a'] . '<br>';
echo 'b:' . $_GET['b'] . '<br>';
會印出
a:1
b:5
// 如果網址有 a 的值,就把 a 印出來
if (isset($_GET['a'])) {
echo 'a:' . $_GET['a'] . '<br>';
}
會印出 a:1
- 4'33" - 9'33": 用表單的方式傳資料到後端(GET)
// 用網址傳參數的方式叫做 GET,跟 http 有關
// action 把表單的內容傳到 data.php 去
<form method="GET" action="data.php">
a: <input name="a" />
age: <input name="age" />
<input type="submit" />
</form>
// 這裡的 form 不用包在 <?php?> 裡面喔
- 另一種寫法
<?php
if (!isset($_GET['name']) || !isset($_GET['age'])) {
echo '資料有缺,請再次填寫<br>';
exit();
}
echo 'Hello!' . $_GET['name'] . '<br>';
echo 'Your age is' . $_GET['age'] . '<br>';
?>
<?php
if (empty($_GET['name']) || empty($_GET['age'])) {
echo '資料有缺,請再次填寫<br>';
exit();
}
echo 'Hello! ' . $_GET['name'] . '<br>';
echo 'Your age is ' . $_GET['age'] . '<br>';
?>
- 9'40" - 最後:用 POST 交換資料:
<form method="POST" action="data.php">
a: <input name="a" />
age: <input name="age" />
<input type="submit" />
</form>
從 PHP 連線到 MySQL 資料庫
- 千萬不要把連線設定檔放上 GitHub,一定要把他排除在版本控制裡面,不然會有帳號密碼資料外洩的問題
// 如何讓 MySQL 跟 PHP 做連線
<?php
$server_name = 'localhost';
$username = '****';
$password = '***';
$db_name = 'christy';
$conn = new mysqli($server_name, $username, $password, $db_name);
// die(): 印出括號內的東西以後,停止執行
if ($conn->connect_error) {
die('資料庫連線錯誤:' . $conn->connect_error);
}
// 用 UTF8 中文就不會是亂碼
// 把時區設為台灣
$conn->query('SET NAMES UTF8');
$conn->query('SET time_zone = "+8:00"');
?>
通常會把連線的部分分開寫,建立一個 conn.php 檔案,裡面只有連線相關資訊(就上面那幾行程式碼)
在其他檔案裡第一行寫,就可以連接了
require_once('conn.php');
5.2 讀取資料:Read
- PHP 與 MySQL 互動
- 讀取資料 「讀取 users 資料庫裡面所有的資料」
- 'select now() from users;' 選取現在時間(MySQL 內建函式)
- 大方向就是:引入連線 -> query 資料庫 -> 處理錯誤 -> fetch 結果 -> 拿到想要的內容
- 讀取資料 「讀取 users 資料庫裡面所有的資料」
<?php
require_once('conn.php');
$result = $conn->query('SELECT * from users;');
if (!$result) {
die($conn->error);
}
$row = $result->fetch_assoc();
print_r($row);
while ($row = $result->fetch_assoc()) {
print_r($row);
}
?>
- 詳細解釋
<?php
// 引入連線
require_once('conn.php');
// 設一個變數來裝結果,用 $conn->query('這裡放 sql 語法') 執行
$result = $conn->query('select * from users;');
// 如果沒有結果,就把錯誤印出來並結束程式
if (!$result) {
die($conn->error);
}
// fetch 拿資料,每執行一次就有一筆資料
// 這樣寫就會有兩筆資料
$row = $result->fetch_assoc();
print_r($row['id']);
print_r($row['username']);
$row = $result->fetch_assoc();
print_r($row['id']);
print_r($row['username']);
// 可以用 while 迴圈把資料都拿出來
// 會印出 Array ( [id] => 1 [username] => Peter ) Array ( [id] => 2 [username] => Nick ) Array ( [id] => 3 [username] => Diana )
while ($row = $result->fetch_assoc()) {
print_r($row);
}
// 會印出
// 1 Peter
// 2 Nick
// 3 Diana
while ($row = $result->fetch_assoc()) {
print_r($row['id'] . ' ' . $row['username'] . '<br>');
}
?>
5.3 新增資料:Create / 包含動態新增
大方向一樣是: 建立連線 -> query 資料庫,使用新增資料的 sql 語法 -> 處理錯誤 -> 拿到想要的資料
sql 原本的語法:「在 users 這個資料庫裡,新增 username 叫 'apple' 的資料」
<?php
require_once('conn.php');
// 實例長這樣
// 但是寫這樣資料一多很容易搞混
$result = $conn->query("INSERT INTO users(username) VALUES('apple');");
if (!$result) {
die($conn->error);
}
print_r($result); // 會印出 1,代表有新增成功
?>
- 也可以這樣寫,但是非常難懂,不推薦
<?php
require_once('conn.php');
$username = 'apple';
// 也可以寫成拼接式,但很難一眼看懂
$sql = "INSERT INTO users(username) VALUES ('" . $username ."')";
echo $sql;
$result = $conn->query($sql);
if (!$result) {
die($conn->error);
}
print_r($result);
?>
最推薦的方法:用 sprintf() 來實現「在 users 這個資料庫裡,新增 username 叫 'apple' 的資料」
- 基本架構:建立連線 -> 設變數 $username = 想要的資料 -> 設變數sql = sprintf("sql 新增語法", $username) -> query 資料庫 -> 處理錯誤 -> 拿到想要的資料
<?php
require_once('conn.php');
$username = 'apple';
$sql = sprintf(
"INSERT INTO users(username) VALUES('%s')",
$username
);
$result = $conn->query($sql);
if (!$result) {
die($conn->error);
}
print_r($result);
?>
- 詳細解釋
<?php
require_once('conn.php');
$username = 'apple';
// 用 sprintf()這個函式來新增資料
// %d is used for numbers (integers)
// %s is used for letters (strings)
// 字串要包起來
// 會按造順序新增,id -> 13, username -> $username
$sql = sprintf(
"INSERT INTO users(id, username) VALUES (%d, '%s')",
13,
$username
);
echo $sql;
$result = $conn->query($sql);
if (!$result) {
die($conn->error);
}
print_r($result);
?>
- 「利用 POST 的方式動態新增 username」
<?php
require_once('conn.php');
if (empty($_POST['username'])) {
die('請輸入 username');
}
$username = $_POST['username'];
$sql = sprintf(
"INSERT INTO users(username) VALUES('%s')",
$username
);
$result = $conn->query($sql);
if (!$result) {
die($conn->error);
}
// 跳轉回原本頁面
header('Location: test.php');
?>
6. 真正的實戰:留言板 - 初階實作篇
- 在實作前:
- 要先想好有哪些資料內容
- 建置資料庫
- 切版
6.1 串接資料庫顯示留言
6.2 新增留言功能
6.3 實作註冊功能
6.4 實作登入功能
6.1 串接資料庫顯示留言
- 把 PHP 寫在 html 最上面
- PHP 程式碼可以放在任何地方
<?php
require_once('conn.php');
$result = $conn->query('SELECT * from comments ORDER BY id DESC');
if (!$result) {
die('error:' . $conn->connect_error);
}
// 下面這一段放在 html 的標籤裡面
while ($row = $result->fetch_assoc()) {
print_r($row);
}
?>
- 在 html 裡面長這樣
<section>
<?php
while ($row = $result->fetch_assoc()) {
?>
<div class="comment">
<div class="comment__avatar">
</div>
<div class="comment__info">
<div class="comment__data">
<div class="comment__nickname">
<?php echo $row['nickname']; ?>
</div>
<div class="comment__time">
<?php echo $row['created_at']; ?>
</div>
</div>
// 這裡縮成一行,留言就不會有空白的問題
// 這兩個 css 屬性很常用
// word-wrap: break-word;
// white-space: pre-line;
<div class="comment__content"><?php echo $row['content']; ?></div>
</div>
</div>
<?php } ?>
</section>
6.2 新增留言功能
- 其實就是動態新增的應用啦
- 要注意幾點:
- 在 form 標籤裡面 action 要寫檔案名稱
<form class="bulletin__post" method="POST" action="handle_new_post.php"></form>
- 留言區塊的 name 要取跟資料庫一樣
<textarea name="content" rows="10"></textarea>
- 在 form 標籤裡面 action 要寫檔案名稱
// handle_new_post.php 檔案內容
<?php
require_once('conn.php');
if (
empty($_POST['nickname']) ||
empty($_POST['content'])
) {
die('請填寫資料');
}
$nickname = $_POST['nickname'];
$content = $_POST['content'];
$sql = sprintf(
"INSERT INTO comments(nickname, content) VALUES('%s', '%s')",
$nickname, $content
);
$result = $conn->query($sql);
if (!$result) {
die($conn->error);
}
header('Location: index.php');
?>
- 如果沒有暱稱、留言內容的情況
- 先把錯誤訊息用 GET 導到另一個網址
- 在 html 裡面放上 php 的程式碼
// handle_new_post.php 檔案內容
<?php
require_once('conn.php');
if (empty($_POST['nickname']) || empty($_POST['content'])) {
// 先把頁面用 GET 的方式導到下面網址
header('Location: index.php?errMsg=資料不齊全');
die('請填寫資料');
}
$nickname = $_POST['nickname'];
$content = $_POST['content'];
$sql = sprintf("INSERT INTO comments (nickname, content) VALUES ('%s', '%s')", $nickname, $content);
$result = $conn->query($sql);
if (!$result) {
die($conn->error);
}
header('Location: index.php');
?>
// 這樣寫的缺點就是網址後面隨便改個內容,網頁顯示的提示就會被取代
<?php
if (!empty($_GET['errMsg'])) {
$msg = $_GET['errMsg'];
echo '<h2>' . $msg . '<h2>';
}
?>
- 比較好的做法(但真正好的做法是在前端用 js 監聽按鈕的方式先處理)
// 在 handle_new_post.php 檔案內容把頁面導到下面網址
if (empty($_POST['nickname']) || empty($_POST['content'])) {
header('Location: index.php?errCode=1');
die('請填寫資料');
}
// 在 html 加上這段程式碼
<?php
if (!empty($_GET['errCode'])) {
$code = $_GET['errCode'];
// 不懂為什麼要放下面這行,因為不寫也可以運作
$msg = 'Error';
if ($code === '1') {
$msg = '資料不齊全';
}
echo '<h2 class="error">' . $msg . '</h2>';
}
?>
6.3 實作註冊功能
- 頁面有:register.php, handle_register.php
- 把註冊頁面切出來
- 註冊功能邏輯:
- 先引入連線 -> 新增註冊的資料(暱稱、使用者名稱、密碼)-> 最後導回首頁
- 要注意的地方:
- 發生錯誤的情形有兩種:
- 帳號已被註冊(資料庫要把 username 選成唯一)
- 請填寫資料(檢查所有欄位是否為空)
- 發生錯誤的情形有兩種:
// handle_register.php 檔案內容
<?php
require_once('conn.php');
if (empty($_POST['nickname']) || empty($_POST['username']) || empty($_POST['password'])) {
// 檢查欄位是否為空
header('Location: register.php?errCode=1');
die();
}
$nickname = $_POST['nickname'];
$username = $_POST['username'];
$password = $_POST['password'];
$sql = sprintf("INSERT INTO users (nickname, username, password) VALUES ('%s', '%s', '%s')", $nickname, $username, $password);
$result = $conn->query($sql);
if (!$result) {
// errno 是經過 mysql 定義的
// 1062 不用包起來
$code = $conn->errno;
if ($code === 1062) {
header('Location: register.php?errCode=2');
}
die($conn->error);
}
header('Location: index.php');
?>
6.4 實作登入功能
- 頁面有:login.php, handle_login.php
- 把登入頁面切出來
- 登入功能邏輯:
- 先引入連線 -> 判斷資料庫裡面是否有一樣的使用者跟密碼 -> 處理錯誤(資料不齊全、查無帳號或密碼)-> 登入成功,導回首頁
- 要注意的地方:
- 登入的邏輯是
$sql = sprintf("SELECT * FROM users WHERE username = '%s' and password = '%s'", $username, $password);
- 如何判斷登入有誤的情形?
- 利用
echo $result->num_rows;
,會輸出資料庫裡面有幾筆資料,輸出為零代表零筆資料,表示登入帳號或密碼有誤 - 輸出為一,代表有一筆資料,表示有找到帳號密碼
- 利用
- 登入的邏輯是
// handle_login.php 內容
<?php
require_once('conn.php');
if (empty($_POST['username']) || empty($_POST['password'])) {
header('Location: login.php?errCode=1');
die();
}
// 登入時輸入的帳密,也是使用 $_POST 的方式
$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);
}
// num_rows 是 sql 內建的語法,判斷結果有多少筆資料
// 當結果為 1,就是有一筆資料
if ($result->num_rows) {
echo '登入成功!';
header('Location: index.php');
} else {
header('Location: login.php?errCode=2');
}
?>
6.5 如何讓瀏覽器用 COOKIE 記錄登入狀態
- 用 setcookie() 這個 function
- PHP setcookie() Function
<?php
$cookie_name = "user";
$cookie_value = "John Doe";
setcookie($cookie_name, $cookie_value, time() + (86400 * 30), "/"); // 86400 = 1 day
?>
// handle_login.php 內容
<?php
require_once('conn.php');
if (empty($_POST['username']) || empty($_POST['password'])) {
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) {
echo '登入成功!';
// 主要寫下面三行
$expire = time() + 3600 * 24 * 30;
setcookie('username', $username, $expire);
header('Location: index.php');
} else {
header('Location: login.php?errCode=2');
}
?>
- 在首頁拿到登入的狀態及資訊:
- 可以先把 Cookie 印出來觀察,會發現是一個 array
- print_r($_COOKIE);
- 接著到index.php 的內容,執行下面的程式碼
$username = NULL;
if (!empty($_COOKIE['username'])) {
$username = $_COOKIE['username'];
}
- 登出功能:
- 讓 cookie 過期,把 username 清空
// logout.php 檔案內容
<?php
setcookie('username', '', time() - 3600);
header('Location: index.php');
?>
- 使用 cookie 紀錄 username 登入情形,去資料庫裡面找到相對應的暱稱
- 關鍵是這兩行
- 把使用者存在 cookie 裡面的風險就是可以隨意偽造身份
$username = $_COOKIE['username'];
$user_sql = sprintf("SELECT nickname FROM users WHERE username = '%s'", $username);
// handle_new-post.php 內容
<?php
require_once('conn.php');
if (empty($_POST['content'])) {
header('Location: index.php?errCode=1');
die('請填寫資料');
}
// 使用 cookie 紀錄 username 登入情形,去資料庫裡面找到 username 的 暱稱
$username = $_COOKIE['username'];
$user_sql = sprintf("SELECT nickname FROM users WHERE username = '%s'", $username);
$user_result = $conn->query($user_sql);
$row = $user_result->fetch_assoc();
$nickname = $row['nickname'];
$content = $_POST['content'];
$sql = sprintf("INSERT INTO comments (nickname, content) VALUES ('%s', '%s')", $nickname, $content);
$result = $conn->query($sql);
if (!$result) {
die($conn->error);
}
header('Location: index.php');
?>
6.6 自己實作通行證機制
- 實作 token 機制
先跳過,之後再回頭學。
6.7 PHP 內建 session 機制
- 要用 session 的話,要在加檔案第一行 -> session_start();
- 只要寫
$_SESSION['username'] = $username;
- 這樣就把 username 存在 session 裡面了
- 一行程式碼做三件事
- 產生 session id (token)
- 把 username 寫入檔案
- set-cookie: 把 session-id 設定到 client 端去
- 登出用 session_destroy();
- PHP 最高!❤️❤️❤️
7. 做個 reddit 自己玩
7.1 版面建置
- 資料庫: redditt_users/reddit_comments
- reddit_users: id/username/password/created_at
- id: int/不用編碼
- username: varchar(64)/utf8mb4_general_ci
- password: varchar(128)/utf8mb4_general_ci
- created_at: datetime/預設值 current_timestamp()
- reddit_comments: id/username/content/created_at
- content: text/utf8mb4_general_ci
- reddit_users: id/username/password/created_at
- 犯的錯誤:
- 登入後跳轉到首頁,發現新增留言時無法一起新增 username,原來是我 handle_new_post.php 功能少寫了抓取 username,這裡要取兩次資料,有點不太習慣語法
- sig up 之後跳轉到首頁,沒辦法拿到 username,所以我要在 handle_signup.php 設 setcookie()。
- 解決了使用者名稱太長,在首頁破版的問題,加上兩行就好了
- word-break: break-all;
- white-space: pre-line;
- 把 RWD 做好了,多虧了這兩行
- width: 100%;
- min-width: 768px;
- 沒想到我能夠自己做出一個來,好開心!
8. 作業參考資料
[教學] 什麼是 Cookie?如何用 JS 讀取/修改 document.cookie?
9. W9 直播檢討
還是有點搞不清楚,以註冊登入系統來說,哪些部分是前端需要驗證而哪些又是後端驗證的呢?
- 前端驗證是為了使用者體驗
- 後端驗證是為了安全性
LIOJ 1053 走迷宮會用到BFS(廣度優先搜尋法),查了一下發現還是看不太懂在寫什麼,老師會建議先去讀演算法的資料,還是等課程在往後學幾週再回來解呢?謝謝
- 可以課程結束後再回來寫,優先順序沒有這麼高
老師解釋前後端概念