iT邦幫忙

DAY 23
3

網站系統規劃實務系列 第 23

網站系統開發 - 發文系統實作(發文篇)

本篇文章作為網站實戰開發的第九篇,我們繼續實作發表文章。

--------系列簡介--------

網站系統可說是現在最多學子與新人想要入門的一個領域,
這個原本屬於新興的領域,這幾年來也累積許多年的知識與 pattern ,
在有限的環境(stateless)與有限的伺服器端、瀏覽器資源下,
成為許多人進入程式的一塊入門鐵板(?)。

這個系列希望能夠就網站系統設計幾個門檻著手,
這是設定給初心者作為學習,給同好們作為回顧,

重新認識有關網站系統的每個環節。
「不要管網站系統了,聽說現在全台灣最多人的職業應該是招換師?」

感謝好同事 LOL 瘋提供時事梗一枚,TPA 這三個單字最近應該是紅翻了。


我們昨天的文章實作到登入狀態,接下來我們就要接著做發文囉!

在進入發文之前,要先提到的就是因為很多操作,是需要限制有使用者身份才能做,
像是發文可能我們就希望使用者一定要登入後才能進行。

還有像是昨天使用者登入前/登入後要顯示不同的功能按鈕等,
所以這時候慢慢的就會進入基本的權限控管,
我們很多操作都需要檢查使用者的登入狀態,這是最最基本的一種作法。

權限控管要注意的基本上無他,就是細心與謹慎思考,


@ 進入點

通常在實作一個新功能,我們會先找到進入點,

以這裡而言,因為發文是我們使用者最主要的操作,
我們先將「發表文章」列在 navigation 上作為進入點吧。

修改檔案:
application\views\_content_nav.php
局部為

<?php /*....略..... */ ?>
<?php if(isset($_SESSION["user"]) && $_SESSION["user"] != null){ ?>
	<ul class="nav pull-right">
      <li><a href="#">Hi <?=$_SESSION["user"]->Account?></a></li>
      <li class="divider-vertical"></li>
      <li><a href="<?=site_url("user/logout")?>">登出</a></li>
      <li><a href="<?=site_url("article/post")?>">發文</a></li>
    </ul>
<?php }else{ ?> 
<?php /*....略..... */ ?>

完整程式碼:https://gist.github.com/3900036

結果如下圖:

@ 畫面實作:

一樣修改之前已經建立好得 view ,並進行處理,
這個 view 的作法跟之前寫登入、註冊很像,我們就先不多做著墨。

application\views\article_post.php

<?php include("_site_header.php"); ?>
<div class="container post">
	<?php include("_content_nav.php") ?>	
	<!-- content -->
	<div class="content">
		<form action="<?=site_url("article/posting")?>" method="post" > 
			<?php if(isset($errorMessage)){ ?>
			<div class="alert alert-error"><?=$errorMessage?></div>
			<?php } ?>
			<table>
				<tr>
					<td>標題</td>
					<?php if(isset($title)){ ?>
						<td><input type="text" name="title" 
							value="<?=htmlspecialchars($title)?>" /></td>
					<?php }else{ ?>
						<td><input type="text" name="title" /></td>
					<?php } ?>
				</tr>
				<tr>
					<td> 內容 </td>
					<td><textarea name="content" rows="10" cols="60"><?php 
						if(isset($content)){
							echo $content;
						}
					?></textarea></td>
				</tr>
				<tr>
					<td colspan="2"> 
						<a class="btn" href="<?=site_url("/")?>">取消</a>
						<input type="submit" class="btn" value="送出" />
					</td>
				</tr>
			</table>
		</form>
	</div>
</div>
<?php include("_site_footer.php"); ?>

完整程式碼:https://gist.github.com/3900058

@ 接著實作 Controller 進行資料驗證與處理,並接著進行資料庫操作。

特別注意在這裡我們依照不同資料表,
建立並呼叫不同 Model,以此進行資料 Model 的責任切分。

還有我們有特別處理使用者權限不足的狀況!

application/controller/article.php

<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class Article extends MY_Controller {

	public function author()
	{
		$this->load->view('article_author');
	}

	public function post(){
		if (!isset($_SESSION["user"])){//尚未登入時轉到登入頁
			redirect(site_url("/user/login")); //轉回登入頁
			return true;
		}
		$this->load->view('article_post',Array(
			"pageTitle" => "發文系統 - 發表文章"
		));	
	}

	public function posting(){

		if (!isset($_SESSION["user"])){//尚未登入時轉到登入頁
			redirect(site_url("/user/login")); //轉回登入頁
			return true;
		}

		$title = trim($this->input->post("title"));
		$content= trim($this->input->post("content"));
		
		if( $title =="" || $content =="" ){
			$this->load->view('article_post',Array(
				"pageTitle" => "發文系統 - 發表文章",
				"errorMessage" => "Title or Content shouldn't be empty,please check!" ,
				"title" => $title,
				"content" => $content
			));
			return false;
		}

		$this->load->model("ArticleModel");
		$insertID = $this->ArticleModel->insert($_SESSION["user"]->UserID,$title,$content);  //完成新增動作;  //完成新增動作
		redirect(site_url("article/postSuccess/".$insertID));
	}	
	public function postSuccess($articleID){
		$this->load->view('article_success',Array(
				"pageTitle" => "發文系統 - 文章發表成功",
				"articleID" => $articleID
		));
	}

	public function edit(){
		$this->load->view('article_edit');	
	}

}

完整原始碼:https://gist.github.com/3900431

另外要特別注意,導向 postSuccess 時,我們用了一個 CI 常用的特別技巧,
我們其實是導向類似這樣的路徑: index.php/article/postSuccess/1

這個 1 是我們剛剛從 ArticleModel 接回來的資料,
那我們在 postSuccess 裡面怎麼接的呢?

是透過 $articleID 去接!

這是 CodeIgniter 預設的 Route 機制,非常簡單而且容易使用,
假設我原本的路徑是 index.php/article/postSuccess/,

如果我傳遞時使用 index.php/article/postSuccess/XXXX/YYYY,
這時我可以透過定義兩個變數來取得 XXXX、YYYY 這兩個資料,
這對於簡易資料的傳遞顯得相當友善。

範例

public function postSuccess($var1,$var2){
//$var1 == "XXXX";
//$var2 == "YYYY";
}

@ 接著建立模組

applciation/models/articlemodel.php

<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class ArticleModel extends CI_Model {
    function __construct()
    {
        parent::__construct();
    }

    function insert($author,$title,$content){
        $this->db->insert("article", 
            Array(
            "Author" =>  $author,
            "Title" => $title,
            "Content" => $content,
            "Views" => 0,
        ));     
        return $this->db->insert_id() ; //回傳剛新增的 Article ID
    }    
}

完整程式碼:https://gist.github.com/3900290

這裡特別注意我們用了一個叫做 insert_id 的函式,
這是針對表單欄位的 AUTO_INCREMENT 欄位。

他可以取得我們最後一筆插入資料的編號值,
所以我們可以知道剛剛新增這篇文章的文章代碼是什麼,並指引使用者前往這篇文章。

接著再建立一個完成的結果畫面程式碼:

application\views\article_success.php

<?php include("_site_header.php"); ?>
<div class="container">
	<?php include("_content_nav.php") ?>
	<div class="content">
		<div class="alert alert-success">
			<!-- Watch up for html injection :p -->
			文章發表成功,<a href="<?=site_url("article/view/".htmlspecialchars($articleID))?>">馬上連往瀏覽!</a>
		</div>
	</div>
</div>
<?php include("_site_footer.php"); ?>

這樣的作法是略嫌有點粗糙就是了,一般會直接將狀態呈現在,
不過就一般撰文而言,應該是夠用了。:P

發文畫面

完成畫面


接著有了文章後,要接著進行文章的呈現,這裡有幾個選擇可以優先考慮,
第一個是先寫單一篇文章的呈現,第二個是寫作者文章列表。

既然我們前面已經有瀏覽單篇文章的連結出現,
接下來我們當然也就要建立文章瀏覽的畫面囉。

@ 一樣是從修改 controlelr 開始

application\controllers\article.php

	public function view($articleID = null){
		if($articleID == null){
			show_404("Article not found !");
			return true;
		}

		$this->load->model("ArticleModel");
		//完成取資料動作
		$article = $this->ArticleModel->get($articleID); 

		if($article == null){
			show_404("Article not found !");
			return true;	
		}

		$this->load->view('article_view',Array(
			//設定網頁標題
			"pageTitle" => "發文系統 - 文章 [".$article->Title."] ", 
			"article" => $article
		));
	}

詳細原始碼:https://gist.github.com/3900551

其中我們用到特別的部份是 show_404 ,
這是 CI 裡面用來呈現 404 (頁面找不到)的輔助函式。

詳細與更多細節,請參考 CI Error Handling Guide

@ 接著實作 model 取資料的部份

application\models\articlemodel.php

    function get($articleID){
    	//CI 裡面跨資料表結合的寫法
        $this->db->select("article.*,user.account");
        $this->db->from('article');
        $this->db->join('user', 'article.author = user.userID', 'left');
        $this->db->where(Array("articleID" => $articleID));
        $query = $this->db->get();

        if ($query->num_rows() <= 0){
            return null; //無資料時回傳 null
        }

        return $query->row();  //回傳第一筆
    }

詳細原始碼:https://gist.github.com/3900544

這裡面我們也示範如何透過 CI 進行跨資料表的操作,
並不算太複雜,是吧? ;)

@ 最後則是 view 與 html 的部份,這部份一樣,就是建立檔案:

application/views/article_view.php

<?php include("_site_header.php"); ?>
<div class="container article-view">
	<?php include("_content_nav.php") ?>	
	<!-- content -->
	<div class="content">
		<table class="table table-bordered">
			<tr>
				<td>作者</td>
				<td><?=htmlspecialchars($article->Account)?></td>
			</tr>
			<tr>
				<td>標題</td>
				<td><?=htmlspecialchars($article->Title)?></td>
			</tr>
			<tr>
				<td> 內容 </td>
				<td><?=nl2br(htmlspecialchars($article->Content))?></td>
			</tr>
		</table>
	</div>
</div>
<?php include("_site_footer.php"); ?>

這裡我們用到一個比較特別的元素,就是 nl2br,
這是可以把換行字元(\n)轉換成 <br > 的一個函式,

這對於基本換行的文字顯示是很有幫助的。

當然,對於 htmlspecialchars 這類能避免 XSS/ Html Injection 的東西,
我們也就只能繼續一再的宣導了,請大家要盡量記得!:)

我們在前端章節時,將再回頭討論 TinyMCE 這類 text editor 跟 XSS 防範細節。

這是目前文章顯示頁面的結果:

到目前為止我們實作了註冊、登入、發文、文章顯示這些細節,
也慢慢越來越接近整體的完善,接下來我們還會繼續討論更多細節,

包括修改如何實作、清單如何實作,那麼我們明天見囉。


上一篇
網站系統規劃 - 發文系統實作(登入篇)
下一篇
網站系統開發 - 發文系統實作(完)
系列文
網站系統規劃實務27
0
老鷹(eagle)
iT邦高手 1 級 ‧ 2012-10-17 11:13:58

沙發
第一沙發~~!

0
l7960261
iT邦新手 5 級 ‧ 2013-04-11 09:12:52

你好,有個疑惑想請教。
在發文成功的Code中

public function postSuccess($articleID){ ... }

使用 Route 機制去接網址後面的參數我了解。
但是我不太了解之後的

public function view($articleID = null){ ... }

這也是用 $articleID 去接變數但是為什麼又要將它宣告為 null ?

tony1223 iT邦新手 2 級 ‧ 2013-04-13 23:34:24 檢舉

那是預設值的意思,可以不設定預設值是 null,
設定預設值自己處理 404 ,而不倚賴 CI 預設行為是我自己開發的習慣。:)

l7960261 iT邦新手 5 級 ‧ 2013-04-18 08:07:55 檢舉

了解,受教了。感謝你解答我的疑惑^^

0
i38383867
iT邦新手 5 級 ‧ 2013-09-06 17:33:07

您好,
我也是照著步驟實做,
後來發現/views/article_view.php的第9行
<td><?=htmlspecialchars($article->Account)?></td>
這裡我要改成
<td><?=htmlspecialchars($article->account)?></td>
才可以正常顯示帳號, 大小寫之分, 不曉得有沒有人遇到相同的問題

我是在article.php > view 印出來看才發現的
var_dump($article);

請問如果我想要印出sql query出來debug該怎麼做呢?
謝謝

janet1 iT邦新手 5 級 ‧ 2013-09-24 10:58:39 檢舉

沒有這個問題呢
照TonyQ的程式碼複製貼上
畫面都可以出來!!!

0
jack1234552000
iT邦新手 5 級 ‧ 2018-12-29 22:57:40

在Articlemodel的get方法那段
function get($articleID){
//CI 裡面跨資料表結合的寫法
$this->db->select("article.*,user.Account");
$this->db->from('article');
$this->db->join('user', 'article.author = user.userID', 'left');
$this->db->where(Array("articleID" => $articleID));
$query = $this->db->get();

    if ($query->num_rows() <= 0){
        return null; //無資料時回傳 null
    }

    return $query->row();  //回傳第一筆
}

user.Account那邊要大寫
不然就會出現像樓上那樣的錯誤
乾 找好久

我要留言

立即登入留言