# 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