# [BE101] 用 PHP 與 MySQL 學習後端基礎
###### Date : 2021 Jun. 07 - Jun.24
#### 安裝
- [XAMPP : Apache + MariaDB + PHP + Perl](https://www.apachefriends.org/zh_tw/index.html)
#### 基礎
- php
- 使用 `<?php` "你要的內容" `?>`
```php
<?php
$a = "Hello"
$b = "World"
echo $a . $b
# HelloWorld
?>
```
- 判斷 : if () {...} else if () {...} else {...}
- 迴圈 : for($i = 1; $i < 10; $i++) { echo $i; }
- 函式 : function add($a, $b) { return $a + $b; }
- 陣列 :
```php
$arr = array("aaa", 1, 2, 3, "bbb");
echo "length: " . sizeof($arr) . <br>;
echo $arr[sizeof($arr) - 2];
var_drmp($arr); # 印出 index, type, value
print_r($arr); # 印出 index, value
```
- apache
- server 端,接收 request 處理完回傳 response
- request => apache => php => output => apache => response
- 預設路徑是在 htdocs 下面
- 資料庫系統
- Relational database : MySQL, PostgreSQL
- NoSQL (not only SQL) : MongoDB, MSSQL
- 在 Oracle 收購 MySQL 之後,怕有閉源風險,而有了 MariaDB ,兩者使用上是差不多的
- CLI => mysql connect
- GUI => phpMyAdmin ... 等
- Table
- schema ( 結構 ) : 名稱、型態、AI ... 等
- primary key ( PK , 主鍵 ) : unique, non-empty
- MySQL
- 查詢 : SELECT * FROM \`table\` WHERE id = 2
- 新增 : INSERT INTO \`table\` (username, content) VALUES ("benben", "helloworld")
- 修改 : UPDATE \`table\` SET username="Benben" WHERE id = 1
- 刪除 : DELETE FROM \`table\` WHERE id = 2
#### 初探 PHP
- disable cache : 取消暫存 ( F12 開發人員工具 )
- GET & POST
- 盡量不要混用,使用 $_GET , $_POST 取得 parameter
- isset($_GET['name']) ,判斷有沒有設置
- empty($_POST['age']),判斷是否為空值
- exit(); 結束 php 程式碼
- PHP 連線到 MySQL 資料庫
- conn.php
```php
<?php
$server_name = 'localhost';
$username = 'benben';
$password = 'benben';
$db_name = 'benben';
$conn = new mysqli($server_name, $username, $password, $db_name);
if ($conn->connect_error) {
die('資料庫連線錯誤:' . $conn->connect_error);
}
# die 印出錯誤並停止之後的 php 檔案
$conn->query('SET NAMES UTF8');
$conn->query('SET time_zone = "+8:00"');
# 可以決絕大部分亂碼問題
?>
```
- 使用 `require_once('conn.php');` 引入就可以連線了
- PHP 與 MySQL 互動 : 讀取資料
- index.php
```php
<?php
require_once('conn.php');
$result = $conn->query("SELECT * FROM users");
if (!$result) {
die($conn->error);
}
while ($row = $result->fetch_assoc()) {
echo "id:" . $row['id'] . '<br>';
echo "username:" . $row['username'] . '<br>';
}
?>
```
- $result = $conn->query("select now()"),可以取得時間
- PHP 與 MySQL 互動 : 新增資料
- insert.php
```php
<?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: index.php");
# 跳轉回 index.php
?>
```
- $result = $conn->query("SELET * FROM users ORDER BY id ASC"); ,從小到大排序 ( DESC 從大到小排序 )
- PHP 與 MySQL 互動 : 刪除資料
- delete.php
```php
<?php
require_once('conn.php');
if (empty($_GET['id'])) {
die('請輸入 id');
}
$id = $_GET['id'];
$sql = sprintf(
"DELETE FROM users WHERE id = %d",
$id
);
echo $sql . '<br>';
$result = $conn->query($sql);
if (!$result) {
die($conn->error);
}
if ($conn->affected_rows >= 1) {
echo '刪除成功';
} else {
echo '查無資料';
}
# 印出被影響的 rows
# header("Location: index.php"); ?>
```
- 先在 index.php 加上刪除的 button
```php
<?php
while ($row = $result->fetch_assoc()) {
echo "id:" . $row['id'] . '<br>';
echo " <a href='delete.php?id=" . $row['id'] . "'>Del</a>";
echo "<br>"
echo "username:" . $row['username'] . '<br>';
}
?>
```
- PHP 與 MySQL 互動 : 編輯資料
- 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");
?>
```
#### 基礎實戰 : Job board
- 思考資料結構
- 前端切板
- 設定資料庫 database
- 基本 CRUD
- 進階 : 加上職缺到期日
- 進階 : 排序功能
- 魔王 : 會員註冊並管理職缺
- 魔王 : 管理員可以審核職缺
#### 基礎實戰 : Blog
- 思考資料結構
- 設定資料庫 database
- 基本 CRUD
- 後台管理、分類
- 前端切板
- 串接整理
- 進階 : 新增草稿
- 進階 : 關於我管理
- 進階 : 評論功能
- 魔王 : 一篇文章多個分類
- 魔王 : 搜尋功能
#### 真正實戰 : 留言板 - 初階實作篇
- 思考資料結構
- 設定資料庫 database
- 前端切板
- 基本 CRUD
- 規劃會員資料、database
- 實作 : 註冊
- 實作 : 登入
- 實作 : Cookie
#### 真正實戰 : 留言板 - 修正問題篇
- 永遠不要相信 client 端的資料
- 問題一 : 偽造身份
- 修正 : 通行証機制
- 使用 token ( 實作 sesstion 機制 )
```php
<?php
function generateToken() {
$s= '';
for ($i = 1; $i < 16; $i++) {
$s .= chr(rand(65,90))
}
return $s;
}
?>
```
- PHP 內鍵 sesstion
```php
<?php
sesstion_start()
# 只要有用到 sesstion 的地方都要加
$_SESSTION['username'] = $username;
# 1. 產生 sesstion id (token)
# 2. 把 username 寫入檔案
# 3. set-cookie: sesstion-id
sesstion_destory()
# 刪除 sesstion 登出
?>
```
- 問題二 : 明文密碼
- 修正 : 內鍵 hash 函式
```php
<?php
$password = password_hash($_POST['password'], PASSOWRD_DEFAULT);
if(password_verify($password, $row['password'])){
# 登入成功
}
?>
```
- 加密 & 雜湊 ( hash )
- 加密可以還原,雜湊不行
- 問題三 : XSS ( Cross-site scripting )
- 原理 : tag <> 的方式來執行 script
- 修正 : HTMLescape
```php
<? php
function escape($str) {
return htmlspecialchars($str, ENT_QUOTES)
}
?>
```
- 問題四 : SQL Injection ( 駭客的填字遊戲 )
- 原理 : 使用 `成對的 ''` 跟 `註解 #` 的機制來實現想達成的 query 或是 subquery
- 修正 : prepared statement
```php
<?php
$sql = "INSET INTO comments(nikname, content) VALUE (?, ?)";
$stmt = $conn->prepare($sql);
$stmt->bind_param('ss', $nickname, $content);
$result = $stmt->excute();
$result = $stmt->get_result();
# 要加這一行才會真正把 result 拿出來
?>
```
#### 真正實戰 : 留言板 - 新增功能篇 & 修正新問題
- 新增功能
- 編輯暱稱
- 編輯留言
- 刪除留言
- 分頁功能
- 編輯暱稱
```php
<form medthod="POST" action="update_user.php">
新的暱稱 : <input type="text" name="nickname" />
<input type="submit" />
</form>
```
- 資料庫正規化
#### 真正實戰 : 留言板 - API 篇
- 取得文章 API
```php
<?php
# 拿資料 $result
# 把資料放入 $comment array 類似 JavaScript 的物件
$comment = array()
while ($row = $result->fetch_assoc()) {
array_aush($comment, array(
"id" => $row["id"],
"username" => $row["aaa"],
"content" => $row["123"]
));
}
# 把 $comment 放入 $json Array
$json = array(
"comments" => $comments
);
# 把 $json 編碼成 JSON 格式
$response = json_encode($json);
header('Content-type:application/json;charset=utf-8');
echo $response;
?>
```
- 新增文章 API
```php
<?php
header('Content-type:application/json;charset=utf-8');
# conn.php ...等
# 如果資料為空
if (empty($_POST['content'])) {
$json = array(
"ok" => false,
"message" => "Please input content!"
)
$response = json_encode($json);
echo $response;
die();
}
# TODO
# -- 如果操作失敗
if (!$result) {
$json = array(
"ok" => false,
"message" => $conn->error
);
$response = json_encode($json);
echo $response;
die();
}
# -- 如果操作成功
$json = array(
"ok" => true,
"message" => "success"
);
$response = json_encode($json);
echo $response;
?>
```
- 前端串接
```javascript
const request = new XMLHttpRequest()
request.open('GET', 'api_comments.php', true)
request.onload = function() {
if (this.status >= 200 && this.status <400) {
const res = this.response
const json = JSON.parse(res)
console.log(json)
// TODO
}
}
request.send()
```
---
#### 心得
- 學完了 php 的 CRUD 功能,大概知道為什麼大家這麼討厭它了,因為它跟 HTML, CSS, JavaScript 全部滲在一起做成灑尿牛丸了,然後檔名我該叫什麼?好問題。不管,只要有 php 的一律都叫 .php 吧,我就爛👍
- 留言板一步一步跟著實作出來,比較知道在幹嘛了XD,但是還是整個很混亂的狀態,可以還要在多看幾次,希望可以跟老師一樣流暢
- 畫了留言板的結構圖 :

- 上完課後發現還不是對 php 很懂,大家好像也都是這樣 XD,老師也說從這周開始會是比較亂的部分,應該會持續到 week 14 的部署大魔王,到了 week 17 18 開始才會介紹後端框架,在那之前先撐一下吧 ~~~
#### 以下更新
- isset()
```php
<?php
$expected_array_got_string = 'somestring';
var_dump(isset($expected_array_got_string['some_key']));
var_dump(isset($expected_array_got_string[0]));
var_dump(isset($expected_array_got_string['0']));
var_dump(isset($expected_array_got_string[0.5]));
var_dump(isset($expected_array_got_string['0.5']));
var_dump(isset($expected_array_got_string['0 Mostel']));
?>
# bool(false)
# bool(true)
# bool(true)
# bool(true)
# bool(false)
# bool(false)
```
- SQL 的編碼問題 (非database) :
- ERROR 1267 (HY000): Illegal mix of collations (utf8_general_ci,IMPLICIT) and (utf8_unicode_ci,IMPLICIT) for operation ‘=’
- Ref : https://dba.stackexchange.com/questions/24587/mysql-illegal-mix-of-collations
```php
$stmt = $conn->prepare(
"SELECT
P.id AS id,
P.content AS content,
P.title AS title,
P.created_at AS created_at,
U.nickname AS nickname,
U.username AS username
FROM
Benben_blog AS P
LEFT JOIN
Benben_users AS U
ON
P.username COLLATE utf8_unicode_ci = U.username COLLATE utf8_unicode_ci
WHERE
P.is_deleted IS NULL"
);
```