วันอังคารที่ 30 พฤศจิกายน พ.ศ. 2553

$zend{'2.1-day'}='create project';

ถึงเวลาเริ่มกันหรือยัง สำหรับ Zend Framework หลังจากที่ เวอร์ชั่น 1.0.0 RC1 ออกได้ซักระยะ ตอนนี้ก็มีเวอร์ชั่น 1.0.0 RC2 ออกตามมีอีกติดๆ (ออกเมื่อ 2007/06/07 นี่เอง) การเปลี่ยนแปลงหลักๆ ดูได้ที่นี่ ที่แน่ๆ มีต้องเปลี่ยน code เล็กน้อย จาก RC1 เป็น RC2
ผมเคยเขียนถึง Getting Start with Zend Framework แล้วครั้งนึง ตอนที่เริ่ม launch php zealots แรกๆ ที่ http://www.phpzealots.com/node/15 แต่ไม่ได้มีรายละเอียดอะไรเลย แถมไปเอา Tutorial ต้นฉบับในรูปแบบ PDF มา attach ไว้ในบทความ ซึ่งเป็นการไม่เหมาะสมอย่างยิ่ง มาคราวนี้ก็เลยมาเขียนในแบบของผมเอง ...

ทำไมผมถึงเลือกใช้ Zend Framework?

  • เพราะหลงเชื่อคารมของ Zend ที่เขาพูดถึง Framework ของเขาเอาไว้ว่า: "Zend Framework เป็น framework ที่เปี่ยมไปด้วยคุณภาพ และเป็น open source สำหรับการพัฒนา Web Applications และ Web Services ด้วย PHP ซึ่งถูกผนวกเข้ากับจิตวิญญาณที่แท้จริงของ PHP นอกจากนี้ Zend Framework ยังมาพร้อมกับความสะดวกสบายในการใช้งาน และฟังก์ชั่นการทำงานต่างๆ ที่ทรงพลัง ทั้งยังได้ตระเตรียมโซลูชั่นต่างๆ สำหรับการสร้างเว็บไซต์ที่ทันสมัย แม่นยำ และปลอดภัย" (ที่มา...คลิก)
  • Code เป็นระเบียบ เรียบร้อย เข้าใจง่าย แบ่งแยก MVC ออกจากกันอย่างชัดเจน
  • URL ดูดี เพราะรองรับ clean url
  • Zend เปิดโอกาสให้เราขยาย หรือ ยกเลิกไม่ใช้งานบางส่วน และเอา library อย่างอื่นมาใช้งานร่วมได้ เช่นผมเอา Smarty Template Engine มาแทน Zend_View เพราะใช้ smarty จนติดแล้ว เป็นต้น
หมายเหตุ:
    * not clean url เช่น http://www.phpzealots.com/?q=node/47
    * clean url เช่น http://www.phpzealots.com/node/47
อันนี้เป็นความเห็นส่วนตัวของผม หลังจากได้ลอง (เพิ่งลอง ยังไม่ได้ใช้งานจริง) ใช้งานพื้นฐานดู แต่ก่อนผมเขียน app ในแบบ MVC เหมือนกัน แต่ตัว Controller จะไม่ชัดเหมือนอย่าง Framework เขา แต่ใช้ switch-case ในการ control action ต่างๆ เอา พอมาเจอ controller แบบชัดๆ แบบนี้ก็เลยชอบ

เกริ่นนำ

การเขียนโปรแกรมสมัยก่อน หรือสมัยเริ่มแรกๆ ของ PHP ด้วยคุณสมบัติ HTML Embedded เราคงคุ้นเคยกับการเขียน code php ปนกันกับ html หรือ wml ตัวอย่างเช่น

<?php include "common-libs.php";
include "config.php";
mysql_connect($hostname, $username, $password);
mysql_select_db($database); ?>
<?php  include "header.php"; ?>
<h1>Home Page</h1>
<?php $sql = "SELECT * FROM news"; $result = mysql_query($sql); ?>
<table>
<?php  while ($row = mysql_fetch_assoc($result)) {  ?>
<tr>
<td> <?php echo $row['date_created']; ?> </td>
<td> <?php echo $row['title']; ?> </td>
</tr> <?php  } ?>
</table>
<?php include "footer.php"; ?>

และแล้วกาลเวลาก็เป็นเครื่องพิสูจน์ให้เห็นว่า การเขียนโปรแกรมแบบนี้ ที่บางคน (โดยเฉพาะคนที่เคยเขียนแค่โปรแกรมเล็กๆ) มองว่ามันก็ง่ายดี ไม่ซับซ้อนเห็นได้ชัดเจน แต่ในความเป็นจริงแล้ว ใน web application จริงๆ ซึ่งมีหน้าตา และการทำงานที่ใกล้เคียงกับ desktop application เข้าไปทุกที ทำให้ระบบมีขนาดใหญ่ มีการทำงานที่ซับซ้อนขึ้น ถ้าเขียนแบบสมัยก่อน ในการดูแล แก้ไข ปรับปรุง จะทำได้ค่อนข้างยุ่งยาก และเสี่ยงมาก เพราะต้องไปเปิด source code ที่มีจำนวนมาก แล้วมาไล่ และแก้ไขเป็นจุดๆ ไป
จึงได้มีคนพยายามคิดหาวิธี ในการออกแบบทางสถาปัตยกรรม ขึ้น ในรูปแบบ (pattern) ต่างๆ กันออกไป และ pattern ตัวหนึ่ง ที่ได้รับความนิยมกันอย่างแพร่หลายคือ MVC - Model View Controller
Zend Framework ได้ถูกออกแบบมาโดยใช้สถาปัตยกรรม MVC นี้ ทำให้ระบบที่ใช้ Zend Framework นี้ จะถูกแบ่งออกเป็น 3 ส่วนอย่างชัดเจน ได้แก่
  • ส่วนของ Model หรือ Zend_model
  • ส่วนของ View หรือ Zend_View และ
  • ส่วนของ Controller หรือ Zend_Controller
ในการแยกแต่ละส่วนออกจากกันนี้ รวมไปถึงการแยก source code ออกจากกันเลย และไว้ไว้คนละ directory กันเลย เพื่อความเป็นระเบียบ และชัดเจน

มาเริ่มกันเลยดีกว่า

พูดพร่ามกันยืดยาว ถ้ายังไม่เห็นภาพ ก็ลองมาดู code กันเลยดีกว่า ... บางท่าน (รวมผมคนนึงแหล่ะ) อาจจะชอบดู source code มากกว่าอ่าน instruction ยืดยาว ก็มาว่ากันเลยแล้วกัน

Requirement

สำหรับ PHP5+MySql+Apache สำหรับใช้ทดสอบในเครื่อง local ดาวน์โหลด AppServe  มาลงก็ได้นะครับ ง่ายดี

โครงสร้าง Directory

ก่อนอื่นให้สร้าง directory ชื่อ zf-tutorial ไว้ก่อน ผมอิงจาก tutorial ของคุณ Rob Allen นะครับ ซึ่งจะใส่การอ้างอิงถึงไว้ในตอนท้าย)
จากนั้น สร้าง sub directory ตามนี้
zf-tutorial/
        /application
            /controllers
            /models
            /views
                /filters
                /helpers
                /scripts
        /library
        /public
            /images
            /scripts
            /styles   
จริงๆ ทาง Zend Framework เขาไม่ได้กำหนด กฏเกณฑ์ตายตัวอะไร ในการออกแบบโครงสร้างของ directory แต่ที่นี้ ขอใช้แบบนี้น้ะครับ เพราะว่าง่าย และชัดเจนดี

ดาวน์โหลด Zend Framework

จากนั้น ให้ไปดาวน์โหลดชุด library class ของ Zend Framework ได้จาก http://framework.zend.com/download ในที่นี้ผมขออ้างถึงตัว " Zend Framework 1.0.0 Release Candidate 1" นะครับ ให้ดาวน์โหลดมา และแตก zip ไฟล์ จะได้ folder ZendFramework-1.0.0-RC1 ออกมา ให้ทำการ copy folder ชื่อ Zend ที่อยู่ใน ZendFramework-1.0.0-RC1/library เอาไปวางไว้ใน zf-tutorial/library ของเรา
ใน zf-tutorial/library ของเราก็จะมี folder Zend อยู่โดดๆ ตัวเดียว ถ้าคลิกเข้าไปดูใน folder นี้ก็จะเห็นชุด class library ของ Zend Framework อยู่เต็มไปหมด

สร้าง .htaccess สำหรับ Clean Url

สร้างไฟล์ .htaccess เพื่อใช้งาน mod_rewrite และทำ clean url เพราะ Zend_Controller ถูกออกแบบมาให้ทำงานกับ clean url
สร้างไฟล์:zf-tutorial/.htaccess
ข้อมูลในไฟล์:
    RewriteEngine on
    RewriteRule .* index.php

เริ่มเขียน Code

1. index.php สำหรับเป็น bootstrap หรือไฟล์เริ่มต้น

สร้างไฟล์:zf-tutorial/index.php
ข้อมูลในไฟล์:
-------------------------
<?php error_reporting(E_ALL|E_STRICT);
date_default_timezone_set('Europe/London');
set_include_path('.' . PATH_SEPARATOR . './library' . PATH_SEPARATOR . './application/models/' . PATH_SEPARATOR . get_include_path());
 
include "Zend/Loader.php";
Zend_Loader::loadClass('Zend_Controller_Front');
Zend_Loader::loadClass('Zend_Config_Ini');
Zend_Loader::loadClass('Zend_Registry');
Zend_Loader::loadClass('Zend_Db');
Zend_Loader::loadClass('Zend_Db_Table');
 
// load configuration $config = new Zend_Config_Ini('./application/config.ini', 'general');
$registry = Zend_Registry::getInstance();
$registry->set('config', $config);
 
// setup database // ใน php.ini ถ้ายังไม่มี extension=php_pdo_mysql.dll ก็ให้เพิ่มเข้าไปด้วย หรือถ้าถูก comment ไว้ก็ให้เอา comment ออก   $db = Zend_Db::factory($config->db->adapter,
$config->db->config->asArray());
Zend_Db_Table::setDefaultAdapter($db);
 
// setup controller $frontController = Zend_Controller_Front::getInstance();
$frontController->throwExceptions(true);
$frontController->setControllerDirectory('./application/controllers');
 
// run! $frontController->dispatch();
?>

2. สร้าง configuration file

สร้างไฟล์:zf-tutorial/application/config.ini
ข้อมูลในไฟล์:
-------------------------
[general]
db.adapter = PDO_MYSQL
db.config.host = localhost
db.config.username = zf_test
db.config.password = phpzealots
db.config.dbname = zf_test

3. สร้าง model Album สำหรับข้อมูลของ album ของเรา

สร้างไฟล์:zf-tutorial/application/models/Album.php
ข้อมูลในไฟล์:
-------------------------
<?php
class Album extends Zend_Db_Table
{
    protected $_name = 'album';
}
?>

4. สร้าง views ทั้งหมด 7 files ดังนี้

สร้างไฟล์:zf-tutorial/application/views/scripts/header.phtml
ข้อมูลในไฟล์:
-------------------------
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<title>
<?php echo $this->escape($this->title); ?>
</title> 
<link rel="stylesheet" type="text/css" media="screen" href="<?php echo $this->baseUrl;?>/public/styles/site.css" /> 
</head> 
<body> 
<div id="content">

สร้างไฟล์:zf-tutorial/application/views/scripts/footer.phtml
ข้อมูลในไฟล์:
-------------------------
</div> 
</body> 
</html>

สร้างไฟล์:zf-tutorial/application/views/scripts/index/index.phtml
ข้อมูลในไฟล์:
-------------------------
<?php 
echo $this->render('header.phtml'); 
?> 
<h1><?php echo $this->escape($this->title); ?></h1> 
<p>
<a href="<?php echo $this->baseUrl; ?>/index/add">Add new album</a>
</p> 
<table> 
<tr> 
<th>Title</th> 
<th>Artist</th> 
<th>&nbsp;</th> 
</tr> 
<?php foreach($this->albums as $album) : ?> 
<tr> <td><?php echo $this->escape($album->title);?></td> 
<td><?php echo $this->escape($album->artist);?></td> 
<td> 
<a href="<?php echo $this->baseUrl; ?>/index/edit/id/<?php echo $album->id;?>">Edit</a> 
<a href="<?php echo $this->baseUrl; ?>/index/delete/id/<?php echo $album->id;?>">Delete</a> 
</td> 
</tr> 
<?php endforeach; ?> 
</table> 
<?php 
echo $this->render('footer.phtml'); 
?>

สร้างไฟล์:zf-tutorial/application/views/scripts/index/add.phtml
ข้อมูลในไฟล์:
-------------------------
<?php echo $this->render('header.phtml'); ?> <h1><?php echo $this->escape($this->title); ?></h1> <?php echo $this->render('index/_form.phtml'); ?> <?php echo $this->render('footer.phtml'); ?>

สร้างไฟล์:zf-tutorial/application/views/scripts/index/edit.phtml
ข้อมูลในไฟล์:
-------------------------
<?php echo $this->render('header.phtml'); ?> <h1><?php echo $this->escape($this->title); ?></h1> <?php echo $this->render('index/_form.phtml'); ?> <?php echo $this->render('footer.phtml'); ?>

สร้างไฟล์:zf-tutorial/application/views/scripts/index/delete.phtml
ข้อมูลในไฟล์:
-------------------------
<?php echo $this->render('header.phtml'); ?> <h1><?php echo $this->escape($this->title); ?></h1> <?php if ($this->album) :?> <form action="<?php echo $this->baseUrl ?>/index/delete" method="post"> <p>Are you sure that you want to delete '<?php echo $this->escape($this->album->title); ?>' by '<?php echo $this->escape($this->album->artist); ?>'? </p> <div> <input type="hidden" name="id" value="<?php echo $this->album->id; ?>" /> <input type="submit" name="del" value="Yes" /> <input type="submit" name="del" value="No" /> </div> </form> <?php else: ?> <p>Cannot find album.</p> <?php endif;?> <?php echo $this->render('footer.phtml'); ?>

สร้างไฟล์:zf-tutorial/application/views/scripts/index/_form.phtml
ข้อมูลในไฟล์:
-------------------------
<?php echo $this->render('header.phtml'); ?> <h1><?php echo $this->escape($this->title); ?></h1> <?php if ($this->album) :?> <form action="<?php echo $this->baseUrl ?>/index/delete" method="post"> <p>Are you sure that you want to delete '<?php echo $this->escape($this->album->title); ?>' by '<?php echo $this->escape($this->album->artist); ?>'? </p> <div> <input type="hidden" name="id" value="<?php echo $this->album->id; ?>" /> <input type="submit" name="del" value="Yes" /> <input type="submit" name="del" value="No" /> </div> </form> <?php else: ?> <p>Cannot find album.</p> <?php endif;?> <?php echo $this->render('footer.phtml'); ?><form action="<?php echo $this->baseUrl ?>/index/<?php echo $this->action; ?>" method="post"> <div> <label for="artist">Artist</label> <input type="text" name="artist" value="<?php echo $this->escape(trim($this->album->artist));?>"/> </div> <div> <label for="title">Title</label> <input type="text" name="title" value="<?php echo $this->escape($this->album->title);?>"/> </div> <div id="formbutton"> <input type="hidden" name="id" value="<?php echo $this->album->id; ?>" /> <input type="submit" name="add" value="<?php echo $this->escape($this->buttonText); ?>" /> </div> </form>

5. สร้าง controller

สร้างไฟล์:zf-tutorial/application/controllers/IndexController.php
ข้อมูลในไฟล์:
-------------------------
<?php class IndexController extends Zend_Controller_Action {     function init()     {         $this->view->baseUrl = $this->_request->getBaseUrl();         Zend_Loader::loadClass('Album');     }     function indexAction()     {         $this->view->title = "My Albums";         $album = new Album();         $this->view->albums = $album->fetchAll();     }     function addAction()     {         $this->view->title = "Add New Album";         if ($this->_request->isPost()) {             Zend_Loader::loadClass('Zend_Filter_StripTags');             $filter = new Zend_Filter_StripTags();             $artist = $filter->filter($this->_request->getPost('artist'));             $artist = trim($artist);             $title = trim($filter->filter($this->_request->getPost('title')));               if ($artist != '' && $title != '') {                 $data = array(                     'artist' => $artist,                     'title' => $title,                 );                 $album = new Album();                 $album->insert($data);                 $this->_redirect('/');                 return;             }         }         // set up an "empty" album         $this->view->album = new stdClass();         $this->view->album->id = null;         $this->view->album->artist = '';         $this->view->album->title = '';         // additional view fields required by form         $this->view->action = 'add';         $this->view->buttonText = 'Add';     }     function editAction()     {         $this->view->title = "Edit Album";         $album = new Album();         if ($this->_request->isPost()) {             Zend_Loader::loadClass('Zend_Filter_StripTags');             $filter = new Zend_Filter_StripTags();             $id = (int)$this->_request->getPost('id');             $artist = $filter->filter($this->_request->getPost('artist'));             $artist = trim($artist);             $title = trim($filter->filter($this->_request->getPost('title')));               if ($id !== false) {                 if ($artist != '' && $title != '') {                     $data = array(                         'artist' => $artist,                         'title' => $title,                        );                     $where = 'id = ' . $id;                     $album->update($data, $where);                     $this->_redirect('/');                     return;                 } else {                     $this->view->album = $album->fetchRow('id='.$id);                 }             }         } else {             // album id should be $params['id']             $id = (int)$this->_request->getParam('id', 0);             if ($id > 0) {                 $this->view->album = $album->fetchRow('id='.$id);             }         }         // additional view fields required by form         $this->view->action = 'edit';         $this->view->buttonText = 'Update';     }       function deleteAction()     {         $this->view->title = "Delete Album";         $album = new Album();         if ($this->_request->isPost()) {             Zend_Loader::loadClass('Zend_Filter_Alpha');             $filter = new Zend_Filter_Alpha();             $id = (int)$this->_request->getPost('id');             $del = $filter->filter($this->_request->getPost('del'));             if ($del == 'Yes' && $id > 0) {                 $where = 'id = ' . $id;                 $rows_affected = $album->delete($where);             }         } else {             $id = (int)$this->_request->getParam('id');             if ($id > 0) {                 // only render if we have an id and can find the album.                 $this->view->album = $album->fetchRow('id='.$id);                 if ($this->view->album->id > 0) {                     // render template automatically                     return;                 }             }         }         // redirect back to the album list unless we have rendered the view         $this->_redirect('/');     } }

6. สร้าง database, table และข้อมูลเริ่มต้น


CREATE DATABASE `zf_test` DEFAULT CHARACTER SET utf8; USE zf_test; CREATE TABLE album (     id INT(11) NOT NULL AUTO_INCREMENT,     artist VARCHAR(100) NOT NULL,     title VARCHAR(100) NOT NULL,     PRIMARY KEY (id) ); INSERT INTO album (artist, title) VALUES     ('James Morrison', 'Undiscovered'),     ('Snow Patrol', 'Eyes Open');

7. สร้าง style sheet

สร้างไฟล์:zf-tutorial/public/styles/site.css
ข้อมูลในไฟล์:
-------------------------
body,html {     font-size:100%;     margin: 0;     font-family: Verdana,Arial,Helvetica,sans-serif;     color: #000;     background-color: #fff; } h1 {     font-size:1.4em;     color: #800000;     background-color: transparent; } #content {     width: 770px;     margin: 0 auto; } label {     width: 100px;     display: block;     float: left; } #formbutton {     margin-left: 100px; } a {     color: #800000; }

8. Launch!

โดยเรียก http://localhost/zf-tutorial/ หรือใส่ path ตามที่ได้วาง app ไว้
บทความนี้ ผมเรียบเรียงใหม่ จากต้นฉบับ Getting Started with the Zend Framework By Rob Allen, www.akrabat.com มีเวอร์ชั่น PDF ด้วยครับ ... คลิก
โดยผมเรียบเรียงแบบสรุปๆ ส่วน source ตัวอย่าง ดาวน์โหลดจาก attached file  มาดูก็ได้น้ะครับ
เดี๋ยวคราวหน้าจะมาพูดถึงแต่ละส่วนกัน ว่าอะไรเป็นอะไร ไปยังงัยมายังงัย หรือจะไปอ่าน tutorial ของคุณ Rob Allen ก็ได้น้ะครับ เขาสอนไว้ค่อนข้างละเอียดดีทีเดียว
ขอบคุณ roteee's blog 

ปล.นะครับ ใช้วิธี download code ไปดูดีกว่าครับ

1 ความคิดเห็น:

  1. #fix# จะมี ฟังชันตรง index.php จาก asString เปลี่ยน เป็น toString

    ตอบลบ