本篇文章作為網站實戰開發的第九篇,我們繼續實作發表文章。
--------系列簡介--------
網站系統可說是現在最多學子與新人想要入門的一個領域,
這個原本屬於新興的領域,這幾年來也累積許多年的知識與 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 防範細節。
這是目前文章顯示頁面的結果:
到目前為止我們實作了註冊、登入、發文、文章顯示這些細節,
也慢慢越來越接近整體的完善,接下來我們還會繼續討論更多細節,
包括修改如何實作、清單如何實作,那麼我們明天見囉。
你好,有個疑惑想請教。
在發文成功的Code中
public function postSuccess($articleID){ ... }
使用 Route 機制去接網址後面的參數我了解。
但是我不太了解之後的
public function view($articleID = null){ ... }
這也是用 $articleID 去接變數但是為什麼又要將它宣告為 null ?
那是預設值的意思,可以不設定預設值是 null,
設定預設值自己處理 404 ,而不倚賴 CI 預設行為是我自己開發的習慣。:)
了解,受教了。感謝你解答我的疑惑^^
您好,
我也是照著步驟實做,
後來發現/views/article_view.php的第9行
<td><?=htmlspecialchars($article->Account)?></td>
這裡我要改成
<td><?=htmlspecialchars($article->account)?></td>
才可以正常顯示帳號, 大小寫之分, 不曉得有沒有人遇到相同的問題
我是在article.php > view 印出來看才發現的
var_dump($article);
請問如果我想要印出sql query出來debug該怎麼做呢?
謝謝
沒有這個問題呢
照TonyQ的程式碼複製貼上
畫面都可以出來!!!
在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那邊要大寫
不然就會出現像樓上那樣的錯誤
乾 找好久