使用 CodeIgniter 開發動態網站

原文網址: http://www.ibm.com/developerworks/opensource/library/os-codeigniter/

副標: 使用 MVC framework 和 CRUD 來提昇效率

摘要: 學習如何使用CodeIgniter快速高效地編寫一個動態的網站。你將能夠利用CodeIgniter的框架和內建的 shortcut 編寫所需的MVC程式,藉以對資料庫進行新增、讀取、更新、和刪除 (Create, Read, Update, and Delete, 簡稱 CRUD) 的資料存取。

常用縮寫

  • CRUD: Create, Read, Update, Delete 新增、讀取、更新、刪除
  • HTML: Hypertext Markup Language
  • MVC: Model-View-Controller
  • SQL: Structured Query Language
  • UI: User interface 使用者界面

CodeIgniter 是一個以 PHP 開發的開源的網頁應用程式框架。它可以配合許多資料庫應用程式使用,包括 MySQL、DB2 Express-C、或者其他資料庫。這個框架使用 MVC 設計模式,其主要目的是要將應用程式的資料層 (data layer) 和展示層 (presentation layer) 分割開來。在 Model-View-Controller (模型 - 視圖 - 控制器) (MVC) 模式中, model 管理資料層,負責與資料庫溝通;view 管理展示層,顯示的使用者界面 (UI) 和內容;至於 controller 則是管理 view 和 model 之間的溝通。

本文概述了使用 CodeIgniter 建立一個動態的 Web 應用程式的基礎所必需的步驟。在此假設你已安裝 2.0.1 版或更高版本的 CodeIgniter 以及 4.1 版或更高版本的 MySQL,並且對這兩者也已有基本的瞭解。另外,因為新版的 CodeIgniter 也支援 PHP 的 sqlsrv driver,所以此程式也可和 Microsoft 的 SQL Server 一起正常運作。

網站基礎: Widget 和 CRUD

當然,大部分的動態網站都會和這篇文章中所介紹的例子有所差異 — 在大多數的情況下,是截然不同。然而,所有的動態網站都有兩個共同的關鍵性構件:資料庫和由資料庫 (動態) 存取的資料。 為了概括所討論的主題,我把這個動態存取的資料稱為 widget。所課的 widget 可以是各種不同的事物 — 銷售的書籍、配方、博客的條目、或新聞稿。不管這個 widget 是什麼,它的定義必需是一組具有一致性的資訊。例如,一個配方所需的一組資訊可能包括標題、配料、指示、和 nutritional breakdown。

要從資料庫中讀取一個 widget,你必須先建立它 (然後它可能會被更新或者甚至被刪除)。這就是 CRUD 存在的原因。CRUD 代表管理資料庫中的 widget 所需的四個主要的操作;連同 widget 本身,它們就是任何動態網站的基礎。

設定資料庫

在這篇文章中,你將建立一個 Web 應用程式,用來管理為某個團體的學生和家長的聯絡資訊,例如孩子的運動隊,YMCA的小組、或是學校的班級,這是你將要建立的例子。

首先,替這 widget 建立 data model。這個 widget 所需的資訊如下:

  • 學生姓名 Student's name
  • 家長姓名 Parents' names
  • 住址 Address
  • 城巿 City
  • 州 State
  • 郵遞區號 ZIP or postal code
  • 電話 Phone number
  • E-mail address

為了儲存這個 widget,建立一個叫 student 的 table,其欄位對應於上面的列表中的資訊。建立 MySQL 的資料庫和table 的 script 如 Listing 1所示。

Listing 1. 建立 MySQL 的資料庫和 table 的 script

CREATE DATABASE classroom;
USE classroom;
CREATE TABLE IF NOT EXISTS `student` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `s_name` varchar(64) DEFAULT NULL,
  `p_name` varchar(64) DEFAULT NULL,
  `address` varchar(128) DEFAULT NULL,
  `city` varchar(32) DEFAULT NULL,
  `state` char(2) DEFAULT NULL,
  `zip` char(10) DEFAULT NULL,
  `phone` char(20) DEFAULT NULL,
  `email` varchar(64) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

CodeIgniter 的啟始設定

在建立了資料庫和 table 之後,要對 CodeIgniter 做一些設定,包括 .\application\config\database.php 中的資料庫設定以及 .\application\config\config.php 中的 base URL。針對這篇文章的目的而言,我假設 base URL 是 http://127.0.0.1/codeigniter/。

預設的 controller 和 view

接下來,建立一個預設的 controller 和 view。(當這些都設定好,你就能看到你所寫的任何程式碼的效果了。)對於這個 project,在 .\application\controllers\ 目錄下建立一個叫作 student.php 的 controller,如 Listing 2所示,並且在 .\application\config\routes.php 將其設定為預設的 controller 。

Listing 2. 預設的 controller: Student

<?php
class Student extends CI_Controller {
  function __construct()
  {
    parent::__construct();
  }
 
  function index()
  {
    // display information for the view
    $data['title'] = "Classroom: Home Page";
    $data['headline'] = "Welcome to the Classroom Management System";
    $data['include'] = 'student_index';
    $this->load->view('template', $data);
  }
}
/* End of file student.php */
/* Location: ./application/controllers/student.php */

請注意,在 index() function 中建立了一個叫作 data 的 array,其中包含三個具名的 index:titleheadline、和 include。這個 array 被傳遞給稱為 template 的 view,其為存放 .\application\views\ 目錄下的 template.php (請參見 Listing 3)。

Listing 3. 預設的 view: template.php

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<title><?php echo $title;?></title>
</head>
<body>
<h1><?php echo $headline;?></h1>
<?php $this->load->view($include);?>
</body>
</html>

這個 view 是此網站所有網頁的 HTML 外殼 (shell) 或視覺外包 (visual wrapper)。它包含了標準的 HTML 設置和接受來自 controller 的三個參數 — title、 headline、及 include — 其分別對應於頁面的title、頁面標頭、和 view 的檔名,其中要顯示的內容包含在 view 中。

建立要 include 進來的 view 檔案是初始安裝的最後一個步驟。將它命名為 student_index.php,就是先前在template.php 中宣告的 (如上面的 Listing 2 所示),並將其存放在 .\application\views\ 目錄下。Listing 4 提供了它的程式碼。

Listing 4. Some basic "Hello World" text

<p>Congratulations. Your initial setup is complete!</p>

請注意,其中沒有標準的 HTML 結構標籤。因為 student_index.php 是透過 template.php View 所 include 進來的,因此,它只是 view 的 HTML wrapper 和顯示的一部分。

現在,如果你瀏覽 http://127.0.0.1/codeigniter/,瀏覽器會載入一個頁面,包含頁面標題、歡迎的標題、以及恭禧的訊息。

CodeIgniter 的 shortcut

在一個 project 的開始或建立原型 (prototype) 的階段,我會儘量使用 CodeIgniter 提供的內建 “shortcut”, (例如HTML Table class 以及 Form 和 URL Helper),藉以減少開發和測試的時間。當然,這會犧牲版面或外觀和感覺 (look and feel) 的更精細的控制,但在早期開發的階段,功能應該優先於外觀。

四個 CRUD 操作

現在讓我們來看看 Web 應用程式中的 CRUD 操作。

Creating widgets

完成初始的設定後,你需要撰寫程式來管理這些 widget。由四個 CRUD 操作的第一個開始,即新增 (Create)。此程式碼必須將使用者輸入的資料當成一個 widget 儲存在資料庫中。

就由建立一個對應於 student table 的 HTML form 入手吧,這個 form 的欄位需對應於 student table 的結構。在 .\application\views\ 目錄下建立一個叫作 student_add.php 的 view 檔案,如Listing 5所示。

Listing 5. 產生用於新增操作的 HTML form 的欄位

<?php
echo form_open('student/create');
// an array of the fields in the student table
$field_array = array('s_name','p_name','address','city','state','zip','phone','email');
foreach($field_array as $field)
{
  echo '<p>' . $field.': ';
  echo form_input(array('name' => $field)) . '</p>';
}
// not setting the value attribute omits the submit from the $_POST array
echo form_submit('', 'Add');
echo form_close();

對於這個檔案有兩個地方要注意。首先是它使用了 form_input() function,這是 CodeIgniter 的 Form Helper 的一個 function,是一個用來快速來產生大多數 form 中所需的 HTML 的方式。它接受三個參數:欄位名稱,欄位的值,以及任何額外的資料 (例如 JavaScript)。

第二個要注意的地方是,form_submit() function,它同樣是接受三個參數,但欄位名稱是空字串。這樣可以避免 submit 欄位包含在由form 傳回的 post array 中。這個 model 會使用這個 array 將紀錄新增到資料庫,如果 submit 欄位包含在其中,資料庫的insert 操作就會失敗。

然後,你需要在 Student 的 controller (Listing 6) 加入一個 function 才能夠看到的 HTML 表單。

Listing 6. Student controller 的 add() function

function add()
  {
    $this->load->helper('form');
   
    // display information for the view
    $data['title'] = "Classroom: Add Student";
    $data['headline'] = "Add a New Student";
    $data['include'] = 'student_add';
    $this->load->view('template', $data);
  }

請注意,在這個 function 中載入 CodeIgniter 的 Form Helper,是要在 view student_add.php 中使用的。

現在,如果你瀏覽 http://127.0.0.1/codeigniter/index.php/student/add,瀏覽器會載入 HTML 表單,其中的欄位對應於 student table 的 record 的資料。這時,如果你將 form 送出,將會產生錯誤,因為你還沒有建立一個 function 來接收 form 的 post。要做到這一點,需要在 Student controller 中加入一個 create() function,如 Listing 7 所示。

Listing 7. Student controller 的 create() function

function create()
  {
    $this->load->helper('url');
   
    $this->load->model('MStudent','',TRUE);
    $this->MStudent->addStudent($_POST);
    redirect('student/add','refresh');
  }

這個 function 對應的 URL 是 http://127.0.0.1/codeigniter/index.php/student/create,其首先載入叫作 MStudent 的 model,稍後我們才要建立此 model。然後,執行 addStudent() function,它接受 student_add.php 所建立的 post array。最後,透過 redirect() funciton 將使用者重新導向回 student_add.php 頁面, redirect() 是 URL Helper 的一個功能。

MStudent model 是負責與資料庫中的 student table 進行互動的。在 .\system\application\models 目錄,建立叫作 mstudent.php 的檔案。(這是不錯的作法,以字首或附加的檔名來表示此檔案是一個 model,因此在檔案名稱前加上 m)。其代碼如 Listing 8所示。

Listing 8. 負責資料層的 MStudent model

<?php
class MStudent extends CI_Model{
  // Create student record in database
  function addStudent($data)
  {
    $this->db->insert('student', $data);
  }
}
/* End of file mstudent.php */
/* Location: ./application/models/mstudent.php */

此 model 使用 db->insert() function 將 post array 新增至 student table 中。這個 function 接受兩個參數:table 的名稱和由欄位的名稱和值對應構成的關聯陣列 (associative array)。此 function 是 CodeIgniter 自動載入的 Active Record class 的一部分,這是 CodeIgniter 提供的一個 shortcut,用以縮短開發時間。

此外,如上所述,假如 post array 將 Submit 按鈕包含在內,將其當作一個欄位,將會使 insert 操作失敗,因為在此 table 中沒有叫作 submit 的欄位。

再次瀏覽 http://127.0.0.1/codeigniter/index.php/student/add。這一次,在欄位中輸入一些資料,然後點擊 Submit。頁面將會刷新,並且欄位會被清空。但是,如果你檢查資料庫,將會看到提交的資料已被新增到 student table 中了。

Retrieving (讀取) widgets

第二個 CRUD 操作是讀取 (read):此程式碼只需單純地從資料庫中讀取 widget record。當然,通常也包括顯示這些 record,這也就是為什麼許多人把這操作也稱為 retrieve

MStudent model 中加入一個用來讀取 student table 的 record 的 function,如Listing 9所示。

Listing 9. 用來讀取 student 中所有 records 的 MStudent function

// Retrieve all student records
  function listStudents()
  {
    return $this->db->get('student');
  }

此程式使用 db->get() function,這也是 CodeIgniter 的 Active Record class 的一部分,其會依據傳給此 function 的參數,即 table 名稱 (student) 產生 SELECT * 的 SQL 指令。

接下來,在 Student controller 中新增一個叫作 listing() 的 function,在此 funciton 中載入 MStudent model 以及執行 listStudents() function,如 Listing 10 所示。其結果存放在 data 的 array 中傳遞給 template view。

Listing 10. 建立用來顯示 student 的 record 的 HTML table

  function listing()
  {
    $this->load->library('table');
  
    $this->load->model('MStudent','',TRUE);
    $students_qry = $this->MStudent->listStudents();
  
    // display information for the view
    $data['title'] = "Classroom: Student Listing";
    $data['headline'] = "Student Listing";
    $data['include'] = 'student_listing';
    $data['students_qry'] = $students_qry;
    $this->load->view('template', $data);
  }

最後,還需要一個 view 來顯示這個 HTML table。在 .\application\views\ 目錄下建立一個叫作 student_listing.php 的檔案,如 Listing 11 所示。在這個 view 中使用 table->generate() function 來產生顯示的表格,這也一個 CodeIgniter 的 shortcut,在 HTML Table class 中。它會依據查詢結果的object 產生 HTML table,會將欄位名稱列在 header 行,然後每一個 record 依序列在下面。

Listing 11. 顯示 HTML table

<?php
 // generate HTML table from query results
 $students_table = $this->table->generate($students_qry);
 echo $students_table;

要看此操作的結果,請瀏覽 http://127.0.0.1/codeigniter/index.php/student/listing。你會看到從資料庫中檢索出來的學生列表。現在花些時間使用輸入表單來新增更多的學生記錄,或是使用如 Listing 12 中所示的 SQL script 來達到同樣的目的。

Listing 12. 新增多筆 student record 的 SQL script

INSERT INTO classroom.student
    (id, s_name, p_name, address, city, state, zip, phone, email)
    VALUES
(NULL, 'Peter Green', 'Len & Natalie Green', '480 West Broad Street',
    'Eastbrook Canyon', 'PA', '19104', '(215) 900-2341',
    'greenery@timewarner.dsl.com'),
(NULL, 'Jonah Ross', 'Robert & Linda Ross', '1293 Law Street',
    'Eastbrook Village', 'PA', '19105', '(215) 907-1122', 'ross_boss@gmail.com'),
(NULL, 'Rebecca Dillon', 'Lainie and Howard Dillon', '12 Flamingo Drive',
    'Westbrook Village', 'PA', '19103', '(215) 887-4313', 'ld_1975@yahoo.com'),
(NULL, 'Noah Singer', 'Carolyn & Peter Singer', '393 Green Lake Road, 8th Floor',
    'Eastbrook Village', 'PA', '19105', '(215) 907-2344', 'candp@gmail.com'),
(NULL, 'Trevor Lee Logan', 'Steven Logan', '400 Green Lake Road, 9th Floor',
    'Eastbrook Village', 'PA', '19105-6541', '(828) 299-9885',
    'misterSAL@sbcglobal.net'),
(NULL, 'Audrey Christiansen', 'Lovey Christiansen', '1993 East Sunnyside Lane',
    'Eastbrook Canyon', 'PA', '19104', '(215) 887-5545',
    'lovey@christiansen-clan.com');

在進行下一個 CRUD 操作之前,為了更方便瀏覽此網站,開啟 .\application\views 目錄下的 template.php 檔案,在其中的 <h1> 標籤上方加入一組全域導覽連結 (global navigation links)。此程式碼如 Listing 13 所示。

Listing 13. 全域導覽連結

<div class="navigation">
<?php
  // nav bar
  echo anchor('student/index', 'Home');
  echo (' | ');
  echo anchor('student/add', 'Add a New Student');
  echo (' | ');
  echo anchor('student/listing', 'List All Students');
?>
</div>

此程式碼使用 anchor() function 這個 shortcut,這是 CodeIgniter 的 URL Helper 的一部分。而且,因為 Student controller 中的每一個 function 都會呼叫 template view,所以必需在 Student 的 constructor 中載入 URL Helper (請參見 Listing 14)。另外,也要將 create() function 中的此 function 刪掉,因為不需重複載入此 Helper。

Listing 14. 在 constructor 載入 helper

function __construct()
  {
    parent::__construct();
    // load helpers
    $this->load->helper('url');
  }

Updating (更新) widgets

你現在將要開發第三個 CRUD 操作:更新。對於這一點,此程式必須:

  • 從資料庫讀取一個 widget 的紀錄。
  • 顯示此記錄,以供編輯。
  • 允許使用者提交修改過的資訊寫回資料庫。

首先修改學生列表,在每一行都加上 Edit 選項。您仍然可以使用 HTML Table class 來產生 table 所需的大部分 HTML。然而,你現在需要自行以迴圈來處理資料庫傳回的查詢結果的每一個 object,藉以建立 table 的每一個行 (row),並且加上 Edit 選項。view 程式碼 student_listing.php 的修改細節請參見 Listing 15。

Listing 15. 使用 HTML Table class 來建立 table

   // generate HTML table from query results
    $tmpl = array (
      'table_open' => '<table border="0" cellpadding="3" cellspacing="0">',
      'heading_row_start' => '<tr bgcolor="#66cc44">',
      'row_start' => '<tr bgcolor="#dddddd">'
      );
    $this->table->set_template($tmpl);
   
    $this->table->set_empty("&nbsp;");
 
    $this->table->set_heading('', 'Child Name', 'Parent Name', 'Address',
        'City', 'State', 'Zip', 'Phone', 'Email');
 
    $table_row = array();
    foreach ($students_qry->result() as $student)
    {
      $table_row = NULL;
      $table_row[] = anchor('student/edit/' . $student->id, 'edit');
      $table_row[] = $student->s_name;
      $table_row[] = $student->p_name;
      $table_row[] = $student->address;
      $table_row[] = $student->city;
      $table_row[] = $student->state;
      $table_row[] = $student->zip;
      $table_row[] = $student->phone;
      $table_row[] = mailto($student->email);
      $this->table->add_row($table_row);
    }   
    $students_table = $this->table->generate();

修改後的程式碼具有的好處是能對 HTML 做更精細的控制:

  • 標頭行 (header row) 可以使用適合的欄位名稱以及改變底色。
  • 不會顯示 ID 欄位.
  • 交替的改變相鄰的行 (row) 底色。

接下來,在 Student controller 中加入一個叫作 edit() 的 function,這是 Edit 選項所指向的 function。程式碼如 Listing 16 所示。

Listing 16. Student controller 的 edit() function

  function edit()
  {
    $this->load->helper('form');
    $id = $this->uri->segment(3);
    $this->load->model('MStudent','',TRUE);
    $data['row'] = $this->MStudent->getStudent($id)->result();
    // display information for the view
    $data['title'] = "Classroom: Edit Student";
    $data['headline'] = "Edit Student Information";
    $data['include'] = 'student_edit';
    $this->load->view('template', $data);
  }

這個 Edit 選項會將 student 的 record ID 當作為 URL 的第三個 segment (在 http://127.0.0.1/codeigniter/index.php/ 之後)。uri->segment(3) function (CodeIgniter 的 URL Helper 的一部分),會從 URL 解析此 ID,並將其傳遞到 MStudent model 的某個 function,藉以檢索該筆學生的記錄,程式碼如 Listing 17 所示。

Listing 17. 由資料庫取出一筆 student 的 record

  // Retrieve one student record
  function getStudent($id)
  {
    return $this->db->get_where('student', array('id'=> $id));
  }

然後建立一個叫作 student_edit.php 的 HTML 表單 (form),以供顯示和編輯這筆 student 的紀錄,程式碼如 Listing 18 所示。

Listing 18. 建立用來編較單筆 student 的 HTML form 的 view

<?php
echo form_open('student/update');
echo form_hidden('id', $row[0]->id);
// an array of the fields in the student table
$field_array = array('s_name','p_name','address','city','state','zip','phone','email');
foreach($field_array as $field_name)
{
  echo '<p>' . $field_name.': ';
  echo form_input($field_name, $row[0]->$field_name) . '</p>';
}
// not setting the value attribute omits the submit from the $_POST array
echo form_submit('', 'Update');
echo form_close();

請注意,這個程式檔幾乎和 student_add.php 一樣。有許多程式人員喜歡使用單一的 add/edit 程式檔的架構來取代兩個分開的程式檔。基本上,這只是不同的程式風格,兩種方式各有利弊。

接下來,在 Student controller 加入 update() function,藉以接收 form 的 post 資料,程式碼如 Listing 19 所示。

Listing 19. Student controller 的 update() function

  function update()
  {
    $this->load->model('MStudent','',TRUE);
    $this->MStudent->updateStudent($_POST['id'], $_POST);
    redirect('student/listing','refresh');
  }

最後,修改 MStudent model,加入一個 function 以供更新資料庫中的 student 的紀錄。此功能如 Listing 20 所示。

Listing 20. 更新單筆 student 的 record

// Update one student record
  function updateStudent($id, $data)
  {
    $this->db->where('id', $id);
    // Microsoft SQL Server can't update identity column 'id'
    unset($data['id']);
    $this->db->update('student', $data);
  }

現在已完成更新的操作。你可以瀏覽學生的列表,編輯任何一個學生的紀錄,提交表單,然後在刷新的學生列表中看到更新後的資訊。其中,Microsoft SQL Server 無法更新識別資料行 'id',所以要將 array 中的 'id' 值刪掉。

Deleting (刪除) widgets

最後一個 CRUD 操作是刪除。在這個操作中,要讓使用者能夠從列表中選擇一筆記錄,然後刪除該記錄。你也會想在真的刪除紀錄之前​​確認使用者的意思 (當碰到使用者點擊了錯誤的連接的情況時)。

首先,以 Listing 21 的程式碼取代 Student controller 的 listing() function 中建立 Edit 選項的程式碼。

Listing 21. 在學生列表中加入 Delete 選項

      $table_row[] = '<nobr>' .
        anchor('student/edit/' . $student->id, 'edit') . ' | ' .
        anchor('student/delete/' . $student->id, 'delete',
          "onClick=\" return confirm('Are you sure you want to '
            + 'delete the record for $student->s_name?')\"") .
        '</nobr>';

接下來,在 Student controller 中加入 delete() function ,這是 Delete 選項所指向的 function。代碼如 Listing 22 所示。

Listing 22. Student controller 的 delete() function

  function delete()
  {
    $id = $this->uri->segment(3);
   
    $this->load->model('MStudent','',TRUE);
    $this->MStudent->deleteStudent($id);
    redirect('student/listing','refresh');
  }

最後,修改 MStudent model,在其中加入一個 function 以供刪除一筆紀錄。此 function 如 Listing 23 所示。

Listing 23. 刪除單筆 student 的 record

// Delete one student record
  function deleteStudent($id)
  {
    $this->db->where('id', $id);
    $this->db->delete('student');
  }

現在測試一下。瀏覽學生的列表並且試著刪除一筆學生的記錄。如果在 JavaScript 的提示中點擊 OK,學生名單將會刷新,並顯示該筆紀錄已被刪除。

結論

恭喜!你已經使用 CodeIgniter 完成了一個動態的 Web 網站的基礎。因為使用了 MVC 模式,這網站將展示層(presentation layer) 和資料層 (data layer) 很清楚的分割開來。並且藉由使用 CodeIgniter 所提供的 coding shortcuts,使得開發時間得以加快,也使得程式碼變少。事實上,你有沒有注意到你完成的這個應用程式只使用了一個 controller,一個 model,和五個 view 嗎?這真的是相當高效率的程式碼。