[PHP] 上傳大頭照的功能


Posted by Eshau on 2021-06-16

前言

這幾天在實作留言板,因此想了很多可以加上去的功能,目前這個留言板有註冊、登入、留言等的基本功能,但看著那空無一物的大頭照,就決定來做個讓使用者上傳大頭照的功能。

目標

不想使用原生的 input 按鈕,希望使用者不需要提交就能在圖片上傳之後自動改變大頭照,而在提交之後使用者也可以自由更改大頭照。

改變原生按鈕

會想這樣做是因為看到很多網站,都有很漂亮的按鈕或是樣式包含了上傳圖片的功能,這邊我直接前往這些網站去看他們的程式碼,來了解他們是怎麼做到的,而我發現他們是把上傳圖片的 input 給藏起來,並使用 label 標籤的 for 屬性來使用上傳圖片的功能。

<form method="POST" class="board__upload" id="img-load" 
action="upload.php"  enctype="multipart/form-data">
  <input type="file" class="nodisplay" id="file" name="file" />
  <label for="file" class="upload-body">照片</label>
</form>

這邊是用 nodisplay 這個 class 來隱藏 input 按鈕,這樣就可以自己做一個喜歡的樣式來取代它,不過這邊會有個小問題,那就是只有點擊一部分的地方才可以觸發事件,這邊我認為可能是有東西被擋住了按鈕,因此我加了z-index:1;,就可以順利執行了。

上傳照片即跳轉

我們有一個可以上傳照片的按鈕後,接下來我們要讓表單在圖片上傳後自動跳轉至upload.php,這樣我們就可以對圖片進行分析及處理

document.getElementById('file').onchange = function() {
  document.getElementById('img-load').submit()
}

或是也可以在 input 內加入 onchange 屬性

<input type="file" id="file" class="nodisplay" name="file"
onchange="this.form.submit()" />

處理照片

這邊我們已經跳轉到了 upload.php,首先我們要確認照片是否上傳成功,可以參考 PHP Manual 來對各項錯誤進行判斷。

接下來我們來判斷上傳的檔案的類型,我們不希望使用者上傳「非圖片類型」的資料,因此我們這樣處理

$error = $_FILES['file']['error'];
if ($error === 0) {
  $tmpname = $_FILES['file']['tmp_name']; // 圖片暫存位置
  $username = $_SESSION['username'];
  $type = $_FILES['file']['type']; // 圖片類型
  switch($type) {
  case 'image/jpeg':
    $safeType = true;
    break;
  case 'image/gif':
    $safeType = true;
    break;
  case 'image/png':
    $safeType = true;
    break;
 }
 .
 .
}

接下來我們使用 base64_encode 將圖片轉換成文字檔

if ($safeType) {
    $file = fopen($tmpname, 'rb'); // 以二進位制開啟圖片
    $fileContent = fread($file, filesize($tmpname)); // 讀取文件
    fclose($file); // 關閉圖片
    $fileContent = base64_encode($fileContent);// 將圖片編碼成 Base64 文字
    .
    .
}

上傳至資料庫

這邊因為我們要將 base64 編碼的文件傳至資料庫,因此我們需要 4 個數值 idtypeimageusername,另外 image 必須為 blob 類型,這邊我是使用 longblob 作為圖片存至資料庫的類型。
Imgur

接下來我們做個判斷,判斷使用者是初次上傳照片還是要更換照片

$check = $conn->query("SELECT image FROM headshot WHERE username='$username'");
if ($check->num_rows === 1) {
  $sql = sprintf(
  "UPDATE headshot SET image='%s', type='%s' WHERE username='%s'",
  $fileContent,
  $type,
  $username     
  );
} else {
  $sql = sprintf(
  "INSERT INTO headshot(image, username, type)
    VALUES('%s', '%s', '%s')",
  $fileContent,
  $username,
  $type
  );
}
$result = $conn->query($sql);
header("Location: index.php");

這邊首先用 session 內的 username 判斷該使用者使否已上傳過大頭照,接著分別用 UPDATE 及 INSERT 來對資料庫進行操作,最後跳回首頁。

套用至討論區

現在狀況是討論區的各個使用者的訊息都已經顯示出來了,只差將照片依照不同使用者來套用至頭像,這邊我使用 nickname 去搜尋 username

(utils.php)

require_once('conn.php');
function getHeadShot($nickname) {
  global $conn;
  $result = '';
  $getUsername = $conn->query("SELECT username FROM users WHERE nickname='$nickname'");
  $username = $getUsername->fetch_assoc()['username'];
  $getImageInfo = $conn->query("SELECT image FROM image WHERE username='$username'");
  if ($getImageInfo->num_rows === 1) {
    $img = $getImageInfo->fetch_assoc()['image'];
    $type = $getImageInfo->fetch_assoc()['type'];
    $result = '"data:' . $type . ';base64,' . $img;
  }
  return $result;
}

$result 是個字串,為 Data URI 的格式,Data URI 的格式是 data:[<mediatype>][;base64],<data>。另外這邊考慮的點是如果使用者還未上傳照片,那就回傳空字串,接下我們就可以在「印出每個討論」的迴圈內把圖片一個一個套用至頭像

<?php
  $result = $conn->query("SELECT * FROM comments ORDER BY id DESC");
  while($row = $result->fetch_assoc()) {
    $haveHeadShot = getHeadShot($row['nickname']);
    if ($haveHeadShot) { 
      echo '<img class="comment__avatar" src=' . $haveHeadShot . '" />'; 
    } else { 
      echo '<div class="comment__avatar"></div>';
    }
  }
?>

如此我們就可以依照 haveHeadShot 是否有值來判斷是否使用資料庫照片或是預設,並把照片套用至各個使用者。










Related Posts

[ Note ] Git 交作業

[ Note ] Git 交作業

Day03 - CSS基本視覺格式

Day03 - CSS基本視覺格式

從實際案例看 class 與 function component 的差異

從實際案例看 class 與 function component 的差異


Comments