# 5-9 、 實作登入功能
###### tags: `BE101` `PHP` `2020十一月第一周` `進度筆記` `Lidemy心得`
---
## 實作登入頁面
- 跟註冊功能頁面很相似。
在 login.php 底下:
- 修改登入標題 `<h1 class="board__title">登入</h1>` 。
- 連接 handle_login.php 檔案:
```
<form class="board__new-comment-form" method="POST" action="handle_login.php">
```
---
### 輸入帳號密碼提交後會到哪?
handle_login 檔案內:
- header 導回 `login.php?errCode=1` 。
- 判斷使用者登入成功與否(sql) , 即是判斷資料庫內有無這筆帳號密碼:
```
$sql = sprintf(
"select * from users where username='%s' and password='%s'",
$username,
$password
//這樣可以去找資料庫有無這筆 username 和 password 。
```
因為不會有 duplicate entry 情形,所以刪掉了:
```
$code = $conn->errno;
if ($code === 1062) {
header('Location: index.php?errCode=2');
}
```
- 資料導出後要檢查有無到資料。
```
$result = $conn->query($sql);
if (!$result) {
die($conn->error);
}
if ($result->num_rows) {
// 如果有資料的話登入成功
echo '登入成功!';
} else {
header("Location: login.php?errCode=2");
}
```
- 最後要導回 index.php 。
```
header("Location: login.php?errCode=2");
//如果 errCode=2 表示帳號或是密碼輸入錯誤
```
- 這邊在 handle_login.php 修正後也要回 login.php 修正。
```
$msg = '帳號或是密碼輸入錯誤'
```
- 登入成功後應該要導回首頁,而如何在登入成功後要如何知道登入確實成功了?
- 在 http 中,"首頁"跟"登入成功"是兩個不同的頁面。
- http 是沒有狀態,每發一個 request 都是新的,因此要讓這兩個頁面連接(記住登入狀態)。
---
# 5-10 、 該怎麼記住登入狀態?
###### tags: `BE101` `PHP` `2020十一月第一周` `進度筆記` `Lidemy心得`
## HTTP 無狀態登入機制(stateless)
- 兩個 request 對同一個 index.php 請求時 , server 不知道兩者是否同一個請求。
- Cookie - 可以記錄 HTTP 狀態(可以用 dev tools 看 Cookies)。
- 可以從 Cookie 查 Domain 、 Expire 、 來源...等 。
***
參考資料:
[An overview of HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP/Overview)
[隱藏欄位介紹](https://lucrelin.blogspot.com/2016/11/stateless-protocol-vs-stateful-protocol.html)
https://dotblogs.com.tw/jimmyyu/2010/10/16/difference-between-stateful-and-stateless
[HTTP Cookies 和Session 使用. HTTP 是一個「無狀態 ...](https://medium.com/%E9%BA%A5%E5%85%8B%E7%9A%84%E5%8D%8A%E8%B7%AF%E5%87%BA%E5%AE%B6%E7%AD%86%E8%A8%98/%E7%AD%86%E8%A8%98-http-cookie-%E5%92%8C-session-%E4%BD%BF%E7%94%A8-19bc740e49b5)
***
## 紀錄 HTTP 狀態
==設置 Cookie:==
```
瀏覽器(收到 header 後)會幫忙設置 user_id=1 並存在瀏覽器內後 request 登入 ↔ Server (經由 server 設定的 Set-Response header Cookie: `user_id=1`) ↔ index.php
```
==因此之後訪問同樣頁面,瀏覽器會自動帶上 Cookie :==
- 瀏覽器會自動把符合條件的 Cookie 帶入。
- 條件: 沒有過期 , Domain 跟路徑吻合。
- Domain 只能設置自己底下的 cookie 。
- 不同家不同源的不能彼此干擾(安全性問題,會盜走他人 Cookie)。
- 所以伺服器看到 cookie 後就能知道瀏覽器有設置的 `user_id=1` 並得知是誰。
```
<?php
$value = 'something from somewhere';
setcookie("TestCookie", $value);
setcookie("TestCookie", $value, time()+3600); /* expire in 1 hour */
setcookie("TestCookie", $value, time()+3600, "/~rasmus/", "example.com", 1);
?>
```
- 一樣有 key & value 。
## Cookie 簡介與實作
開啟 handle_login.php 檔案:
- `setcookie()`
```
if ($result->num_rows) {
// 如果有資料的話登入成功
$expire = time() + 3600 * 24 * 30; // 30 day
setcookie("username", $username, $expire); // "字串", $傳進來的值, $過期時間
header("Location: index.php");
} else {
header("Location: login.php?errCode=2");
}
```
- 用 dev tool 看 Network 的 handle_login.php 檔案:
```
Response header 就會是 Set-Cookie 傳回來。
```
- 用 dev tool 看 Application → Cookies 底下就會多了一個 Name 和 Value 。
- 在 handle_login.php 檔案下回傳 Set-Cookie 給瀏覽器。
- 只要在這個 `/be101/board` 資料夾底下的檔案就能共享這個 cookie 。
- 但如果在 `/be101` 底下開了其他資料夾就無法共享 cookie 。
- 如果發現 cookie 失效,有時候要更改其 Pathway 。
- 如果 Path 底下設定成 `/` 就可以是 localhost 底下所有頁面都能共享這個 cookie 。
## 設置 index.php
```
<?php
require_once("conn.php");
$result = $conn->query("select * from comments order by id desc");
if (!$result) {
die('Error:' . $conn->error);
}
$username = NULL; // 帳號不是 no 的話,即登入狀態
if (!empty($_COOKIE['username'])) {
$username = $_COOKIE['username']; // username 即帳號
}
?>
```
- 記住登入狀態。
- 檢查 username 是不是空的:
```
<main class="board">
<div>
<?php if (!$username) { ?>
// 如果 username 是空的話顯示註冊和登入的值
<a class="board__btn" href="register.php">註冊</a>
<a class="board__btn" href="login.php">登入</a>
<?php } else { ?>
<a class="board__btn" href="logout.php">登出</a>
<?php } ?>
</div>
```
- 如果 username 不是空的話就不會顯示註冊和登入按鈕(表示為登入狀態)。
- 已經有登入的話,顯示登出。
## 設置登出功能
- 可以把 cookie 的 `user_id` 清空。
- 把過期時間(expire)設成現在時間 - 3600 , 這樣就變成"過去的時間"規格。
```
time()-3600
// 即過期了
```
logout.php 檔案:
```
<?php
setcookie("username", "", time() - 3600);
header("Location: index.php");
?>
```
過期的 cookie 被拋棄掉後,瀏覽器代的下個 request 就不會有這個 cookie 。
## 如果沒有登入的話看不到底下的留言區塊
index.php 底下:
```
<form class="board__new-comment-form" method="POST" action="handle_add_comment.php">
<textarea name="content" rows="5"></textarea>
<?php if ($username) { ?>
// 如果 username 登入的話就看得到留言表單
<input class="board__submit-btn" type="submit" />
<?php } else { ?>
<h3>請登入發布留言</h3>
<?php } ?>
</form>
```
- 沒有登入的話,會看到"請登入發布留言"文字。
- 因為 nickname 變成從 cookie 中去抓取使用者資訊出來。
- 所以也要更動 handle_add_comment.php 留言板資訊:
```
<?php
require_once('conn.php');
if (
empty($_POST['content'])
) {
header('Location: index.php?errCode=1');
die('資料不齊全');
}
$username = $_COOKIE['username'];
$user_sql = sprintf(
"select nickname from users where username='%s'",
$username
); // 這樣就能從資料庫中去抓出 nickname , 不把 nickname 存在 cookie 內是因為日後可能更改
$user_result = $conn->query($user_sql);
$row = $user_result->fetch_assoc();
$nickname = $row['nickname']; // 還沒有做錯誤處理
$content = $_POST['content']; // 用 POST 會出現確認重新提交表單
$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");
?>
```
- Cookie 可以維持 HTTP 狀態的特性,從 Server 這用 `Set-Cookie()` Header 設置到瀏覽器內存起來。
- Response Header 給瀏覽器後,就會讓瀏覽器設定所要的 Cookie 。
- 在 handle_login.php 檔案下回傳 Set-Cookie 給瀏覽器。
- 瀏覽器接收到這個 Set-Cookie 後就會把想要設定的 Cookie 設定到瀏覽器內。
- 如 Cookie 內設置(紀錄) username 、 expire ...等。
- 之後發 request 到 index.php 時就會把設定的 Cookie 一起帶入。