---
# System prepended metadata

title: 5-9 、 實作登入功能
tags: [Lidemy心得, 進度筆記, BE101, PHP, 2020十一月第一周]

---

# 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 一起帶入。 

