iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 21
0
Modern Web

寫給朋友的 PHP 從 0 到 100 實戰教程系列 第 21

Day 21. PHP教程: 優化架構與實作 flash session 附原始碼

Why
專案寫到這裡只是加上註冊登入,我們的 route.php 已經快到進入萬言書的狀態,日後維護跟新增功能恐怕很麻煩,所以我們要想辦法優化。那怎麼優化呢?一樣先找出共通點,很輕易的可以發現他們都是 switch { case } 的架構。所以我覺得這些 case: 可以抽成一頁頁的 php 檔案,通通放在一個資料夾來管理;另外也發現處理表單的動作常常放在產生畫面的資料邏輯上方。這樣每個檔案由三個區塊構成:表單邏輯、頁面所需的資料邏輯、頁面組合。這在一開始 index.php 的時候,真的很可愛,很好看懂。但是當三個區塊都長大到很恐怖的時候,就只能保佑自己一年後看得懂,保佑你的同事打開檔案的時候沒有立刻走出去抽煙喝咖啡。所以我們可以再把這一坨拆成兩塊:表單邏輯自成一頁、頁面資料與頁面組合放一起。

How
怎麼做呢?我先創一個資料夾 controller 專門放這些頁面,把 route.php 的 case 通通拆出來用 parameter 來命名。

如果是表單邏輯我用 do 開頭來命名
如果是頁面邏輯就用 URL 變數 來命名
https://ithelp.ithome.com.tw/upload/images/20180107/20107394hno7W7npCE.png

route.php 改成用參數決定載入 controller 資料夾下存在的檔案名

<?php
$parameter = strtolower($route->getParameter(1));
$controller_array = scandir('controller');
$controller_array = array_change_key_case($controller_array, CASE_LOWER);

if (in_array($parameter.'.php', $controller_array)) {
  include( 'controller/'.$parameter.'.php' );
}else{
  include( 'controller/login.php' ); // 預設讀取登入頁
}

拆完之後發現一個問題,表單邏輯層如果檢查出資料驗證問題,有錯誤訊息要回傳,該怎麼回傳呢?用 header 導向也不好帶著一堆文字陣列資料回來。於是我發現各家框架都有使用 flash session 它的概念是,這資料只存到下一頁載入後,我覺得根本就是為了解決傳遞錯誤訊息這件事而生的。找到套件直接下指令吧。

composer require plasticbrain/php-flash-messages

官方說明書

https://github.com/plasticbrain/PhpFlashMessages

簡單介紹用法如下,他自帶 bootstrap 的 class 所以如果我們有套 bootstrap 就會自動變漂亮

<?php
$msg = new \Plasticbrain\FlashMessages\FlashMessages();

// Add messages
$msg->info('This is an info message');
$msg->success('This is a success message');
$msg->warning('This is a warning message');
$msg->error('This is an error message 1');
$msg->error('This is an error message 2'); // 可以塞多個

// If you need to check for errors (eg: when validating a form) you can:

if ($msg->hasErrors()) {
	$msg->display(); // 最後一次全部顯示出來
} else {
  // There are NOT any errors
}

效果展示
https://ithelp.ithome.com.tw/upload/images/20180107/20107394ADiSNCNmQc.png

如果我們在 init.php 先全域宣告了這個物件

$msg = new \Plasticbrain\FlashMessages\FlashMessages();

這樣一來我們之前檢查 $error 那段都可以拔掉改成這樣:

<?php if ($msg->hasMessages()) $msg->display(); ?>

view/body/login.php 全文如下

<div class="container">
	<div class="row">
	    <div class="col-xs-12 col-sm-8 col-md-6 col-sm-offset-2 col-md-offset-3">
			<form role="form" method="post" action="<?=Config::BASE_URL?>do_login" autocomplete="off">
				<h2>Please Login</h2>
				<p><a href='register'>Register a New Account</a></p>
				<hr>

				<?php if ($msg->hasMessages()) $msg->display(); ?>

				<div class="form-group">
					<input type="text" name="username" id="username" class="form-control input-lg" placeholder="User Name" value="<?php if($msg->hasErrors()){ echo htmlspecialchars($_POST['username'], ENT_QUOTES); } ?>" tabindex="1">
				</div>
				<div class="form-group">
					<input type="password" name="password" id="password" class="form-control input-lg" placeholder="Password" tabindex="3">
				</div>
				<div class="row">
					<div class="col-xs-9 col-sm-9 col-md-9">
						 <a href='forget'>Forgot your Password?</a>
					</div>
				</div>
				<hr>
				<div class="row">
					<div class="col-xs-6 col-md-6"><input type="submit" name="submit" value="Login" class="btn btn-primary btn-block btn-lg" tabindex="5"></div>
				</div>
			</form>
		</div>
	</div>
</div>

controller/login.php 只剩少少的頁面組合,因為把表單邏輯抽成 do_login.php

<?php
/**
 * 載入頁面
 */
//define page title
$title = 'Login';
include('view/header/default.php'); // 載入共用的頁首
include('view/body/login.php');     // 載入登入用的頁面
include('view/footer/default.php'); // 載入共用的頁尾

在 do_login.php 裡面我把錯誤用迴圈通通送進 $msg 再送回上一頁即可回到登入頁告知錯誤原因

foreach( $error as $e) {
        $msg->error($e);
}
header('Location: ' . $_SERVER['HTTP_REFERER']);
exit;

do_login.php 全部的代碼是這樣

<?php
if(isset($_POST['submit'])) 
{
  $error = array(); 
  $gump = new GUMP();

  $_POST = $gump->sanitize($_POST); 
  $validation_rules_array = array(
      'username'    => 'required|alpha_numeric|max_len,20|min_len,3',
      'password'    => 'required|max_len,20|min_len,3'
  );
  $gump->validation_rules($validation_rules_array);
  $filter_rules_array = array(
      'username' => 'trim|sanitize_string',
      'password' => 'trim',
  );
  $gump->filter_rules($filter_rules_array);
  $validated_data = $gump->run($_POST);

  if($validated_data === false) {
    $error = $gump->get_readable_errors(false);
  } else {
    // basic validation successful
    foreach($validation_rules_array as $key => $val) {
      ${$key} = $_POST[$key]; // trans to local parameters
    }
    $userVeridator = new UserVeridator();
    $userVeridator->loginVerification($username, $password);
    $error = $userVeridator->getErrorArray();

    if(count($error) == 0){
      $table = "members";
      $condition = "username = :username";
      $order_by = "1";
      $fields = "*";
      $limit = "LIMIT 1";
      $data_array = array(":username" => $username);
      $result = Database::get()->query($table, $condition, $order_by, $fields, $limit, $data_array);
      $_SESSION['memberID'] = $result[0]['memberID'];
      $_SESSION['username'] = $username;
      header('Location: home');
      exit;
    }
  }
  if(isset($error) AND count($error) > 0){
    foreach( $error as $e) {
        $msg->error($e);
    }
    header('Location: ' . $_SERVER['HTTP_REFERER']);
    exit;
  }
}else{
  header('Location: ' . Config::BASE_URL);
  exit;
}

效果如圖
https://ithelp.ithome.com.tw/upload/images/20180107/20107394vvNYFhHXHN.png

我也改寫了其他頁面邏輯,包括註冊、開通、驗證重置密碼、忘記密碼等等
因為太多了,就不一一展示,放上 github 給讀者直接下載來參考

https://github.com/JarvisHo/login_register/


上一篇
Day 20. PHP教學: 實作重置密碼流程
下一篇
Day 22. PHP教學: 使用 monolog 來追蹤問題
系列文
寫給朋友的 PHP 從 0 到 100 實戰教程30

尚未有邦友留言

立即登入留言