เนื้อหาตอนที่แล้ว เราได้ทำความรู้จักการสร้าง static page
พร้อมทั้งการกำหนดรูปแบบ หรือ rule ให้กับ routing เบื้องต้น
ทบทวนได้ที่บทความ
สร้าง Static Page เบื้องต้นใน CodeIgniter 4 http://niik.in/993
https://www.ninenik.com/content.php?arti_id=993 via @ninenik
เนื้อหานี้ เราจะมาดูเกี่ยวกับการสร้าง dynamic page หรือ dynamic content ที่
ข้อมูลการแสดงผล สามารถเปลี่ยนแปลงได้ตามที่เรากำหนด หรือต้องการ
รวมถึงจะมาเริ่มการใช้งานร่วมกับ database
ถ้าใครเคยเห็นรูปแบบของ wordpress หรือ blog ต่างๆ ก็พอจะเข้าใจกับรูปแบบของ
dynamic page มากขึ้น เราจะสร้างรูปแบบของหน้าเพจ ข่าว หรือหน้าเพจแสดงข่าวสารอย่างง่าย
โดยจำลองการทำงาน และทำความรู้จักการใช้งาน CI4 เพิ่มขึ้น
กำหนดในส่วนของ Database
ก่อนอื่น เราจะต้องทำการสร้างตารางสำหรับใช้งานข้อมูลก่อน โดยกำหนดรูปแบบโครงสร้างตาราง ดังนี้
CREATE TABLE news ( id int(11) NOT NULL AUTO_INCREMENT, title varchar(128) NOT NULL, slug varchar(128) NOT NULL, body text NOT NULL, PRIMARY KEY (id), KEY slug (slug) );
ในที่นี้เรามีฐานข้อมูลสำหรับทดสอบชื่อ dbci4 เราจะทำการสร้างตารางด้วยคำสั่ง SQL ข้างต้น จะได้เป็น
ตาราง "news" ข้างต้น สังเกตว่าจะมีฟิลด์ "slug" ที่เรากำหนดให้เป็น key ไว้เก็บข้อความที่เราจะใช้เป็นส่วน
หนึ่งของ URL ถ้าใครที่เคยทำหรือจักการทำ SEO ก็น่าจะพอเข้าใจ ส่วนอื่นๆ ที่เหลือก็เป็นค่าทั่วไป มี title สำหรับ
หัวข้อ และ body สำหรับรายละเอียดของข่าว
ต่อด้วยให้เราเพิ่มข้อมูลสำหรับทดสอบหรือข้อมูลจำลองนี้เข้าไป ผ่านคำสั่ง SQL เหมือนเดิม
INSERT INTO news VALUES (1,'Elvis sighted','elvis-sighted','Elvis was sighted at the Podunk internet cafe. It looked like he was writing a CodeIgniter app.'), (2,'Say it isn\'t so!','say-it-isnt-so','Scientists conclude that some programmers have a sense of humor.'), (3,'Caffeination, Yes!','caffeination-yes','World\'s largest coffee shop open onsite nested coffee shop for staff only.');
ในตาราง "news" เราก็จะมีข้อมูลเบื้องต้นประมาณนี้
การเชื่อมต่อและใช้งาน Database
เมื่อเราเตรียมในส่วนของฐานข้อมูลไว้แล้ว ต่อไปก็เป็นส่วนของการเชื่อมต่อ Database กับ CI4 ของเรา โดยในโหมด
developement เราสามารถกำหนดในไฟล์ .env ได้ ประมาณนี้
* เกี่ยวกับไฟล์ .env ดูที่เนื้อหาเริ่มต้น http://niik.in/990
#-------------------------------------------------------------------- # DATABASE #-------------------------------------------------------------------- database.default.hostname = localhost database.default.database = dbci4 database.default.username = root database.default.password = database.default.DBDriver = MySQLi
สร้าง Models แรก
ในตอนที่แล้ว เราได้สร้าง Controller แรก เนื้อหาในตอนนี้ เราก็จะได้สร้าง model แรกกัน ซึ่งในการจัดการข้อมูล
ร่วมกับฐานข้อมูล เราควรกำหนดการทำงานต่างๆ ไว้ใน Model แทน เพราะทำให้สะดวก และง่ายต่อการเรียกใช้งาน
ในภายหลังได้ โดย Model ใน CI4 มีฟังก์ชั่นต่างๆ ในการจัดการข้อมูลมาให้ สามารถเรียกใช้งานได้ง่าย
มาดูคร่าวๆ ว่า Model ใน CI4 ทำอะไรได้บ้างเบื้องต้น ดังนี้
- มีการเชื่อมต่อกับ dababase อัตโนมัติ เราไม่ต้องกำหนดการเชื่อมต่อเพิ่มเติม
- รองรับรูปแบบการจัดการแบบ CRUD methods (การอ่าน เพิ่ม ลบ แก้ไขข้อมูล)
- รองรับรูปแบบการตรวจสอบความถูกต้องของข้อมูล หรือ validation
- จะการรูปแบบการแบ่งหน้าข้อมูลอัตโนมัติ
- และอื่นๆ
ให้เราสร้างไฟล์ app/Models/NewsModel.php แล้วกำหนดโค้ดเบื้องต้นดังนี้ลงไป
app/Models/NewsModel.php
<?php namespace App\Models; // กำหนด namespace use CodeIgniter\Model; // เรียกใช้งาน Model class class NewsModel extends Model // สร้าง Model class โดยสืบทอดจาก Model { protected $table = 'news'; // ใช้ข้อมูลตาราง news }
ไฟล์ NewsModel.php ข้างต้น ดูคร่าวๆ ก็จะคล้ายกับรูปแบบของ controller ในบทความก่อนหน้า
เราทำการสร้าง NewsModel class โดยสืบทอดมาจาก CodeIgniter\Model รวมถึงทำการโหลด database library
มาใช้งานอัตโนมัติ โดยสามารถอ้างอิงการเรียกใช้งานร่วมกับ database ผ่าน object ที่ชื่อ $this->db
ตอนนี้เราเตรียมในส่วนของ database และ model เบื้องต้นแล้ว ต่อไปก็เป็นส่วนของการดึงข้อมูลจาก ตาราง
news มาแสดง อย่างที่เราได้บอกไปแล้วว่า ใน model ของ CI4 มีเครื่องมือที่อำนวยความสะดวกให้เราเรียกใช้งาน หรือที่เรียกว่า
ตัวช่วยการคิวรี่ ให้เราแทรกโค้ดนี้เพิ่มเข้าไปในไฟล์ NewsModel.php จะได้เป็น
app/Models/NewsModel.php
<?php namespace App\Models; // กำหนด namespace use CodeIgniter\Model; // เรียกใช้งาน Model class class NewsModel extends Model // สร้าง Model class โดยสืบทอดจาก Model { protected $table = 'news'; // ใช้ข้อมูลตาราง news public function getNews($slug = false) { if ($slug === false) // เมื่อไม่มีการส่งค่าใดๆ เข้ามา { // จะทำหน้าที่คล้ายการคิวรี่คำสั่ง sql และคืนค่า // SELECT * FROM news return $this->findAll(); } // กรณีมีค่าตัวแปร $slug ถูกส่งเข้ามา // จะทำหน้าที่คล้ายการคิวรี่คำสั่ง sql และคืนค่า // SELECT * FROM news WHERE slug = $slug LIMIT 1 return $this->asArray() ->where(['slug' => $slug]) ->first(); } }
ในโค้ดมีคำอธิบายอยู่แล้ว แต่ขออธิบายแยกเพิ่มเติม จะเห็นว่ามีการกำหนดเงื่อนไขการคิวรี่ข้อมูลอยู่ด้วยกัน 2 ส่วน
คือส่วนที่แสดงข้อมูลโดยมีเงื่อนไขตามค่า slug กับ แสดงข้อมูล ทั้งหมด คำสั่ง findAll() , asArray(), where() และ
first() ล้วนแต่เป็นตัวช่วยคิวรี่ของ Model ใน CI4 หรือที่เรียกว่า Query Builder
ตัวแปร $table เป็น property หนึ่งของ Model ที่ใช้กำหนดชื่อ ตาราง ในฐานข้อมูลที่จะใช้งาน
สมมติเราเรียกใช้ผ่าน $this->db ก็จะเขียนในส่วนของ getNews() method ประมาณนี้
public function getNews($slug = false) { if ($slug === false) // เมื่อไม่มีการส่งค่าใดๆ เข้ามา { $query = $this->db->query(" SELECT * FROM news "); return $query->getResultArray(); } $query = $this->db->query(" SELECT * FROM news WHERE slug = ".$this->escape($slug)." LIMIT 1 "); return $query->getRowArray(); }
แต่ในที่นี้เราใช้รูปแบบของ Model Query Builder ตามรูปแบบโค้ดด้านบนก่อนหน้า
สร้าง Controller เพื่อใช้งานและส่งต่อข้อมูล
เมื่อเราสร้าง model ที่ใช้สำหรับจัดการข้อมูลเพื่อมาใช้งานแล้ว ต่อไปเราก็ต้องมาสร้างตัว controller เพื่อนำข้อมูลที่ได้ไป
ใช้งานหรือแสดงผลใน view โดยกำหนดเงื่อนไข และรูปแบบการทำงานเพิ่มเติม เป็นดังนี้คือ
ในกรณีที่เป็นการแสดงข้อมูลทั้งหมด เราก็จะส่งไปที่ไฟล์ views ที่ชื่อ overview.php ซึ่งเป็นหน้าที่แสดงรายการทั้งหมด
ในขณะที่ถ้าเป็นไฟล์ที่ตรงตามค่า slug เราจะส่งไปแสดงเฉพาะของข้อมูลนั้นๆ ในไฟล์ views ที่ชื่อ view.php เข้าใจง่ายๆ
ก็เหมือนกับ หน้ารวมของข่าวทั้งหมด กับหน้ารายละเอียดของเฉพาะข่าวที่เปิดดู เป็นต้น
เราจะได้ไฟล์ controller เพิ่ม method สองค่าสำหรับจัดการดังนี้
app/Controllers/News.php
<?php namespace App\Controllers; use App\Models\NewsModel; // เรียกใช้งาน NewsModel class use CodeIgniter\Controller; class News extends Controller { // default method สำหรับแสดง ข่าวทั้้งหมด public function index() { // สร้าง instance model จาก model object เพื่อใช้งาน $model = new NewsModel(); // ตัวแปรที่ส่งเข้าไปใน views $data = [ 'news' => $model->getNews(), // ตัวแปร $news จะเป็นข่าวทั้งหมด ที่จะสุ่งเข้าไปใน views 'title' => 'News archive', ]; // ส่วนของการแสดงผล และส่งค่าไปยัง views echo view('templates/header', $data); echo view('news/overview', $data); echo view('templates/footer', $data); } // method สำหรับแสดงข้อมูลข่าว เฉพาะรายการ public function view($slug = NULL) { // สร้าง instance model จาก model object เพื่อใช้งาน $model = new NewsModel(); // กำหนดตัวแปร $news ที่จะส่งเข้าไปใน views ผ่านตัวแปร $data // ให้ทำการดึงข้อมูล ที่มีรวยการที่ค่า slug ตรงกับที่ส่งเข้าไป $data['news'] = $model->getNews($slug); if (empty($data['news'])) // ถ้าไม่ตรง หรือไม่มีในฐานข้อมูล { // ถ้าเข้าเงื่อนไขนี้แล้ว คำสั่งแสดงผลด้านล่างจะไม่ทำงาน throw new \CodeIgniter\Exceptions\PageNotFoundException('Cannot find the news item: '. $slug); } // ถ้ามีข้อมูลกำหนดตัวแปร $title ส่งเข้าไปใน views ผ่านตัวแปร $data $data['title'] = $data['news']['title']; // ส่วนของการแสดงผล และส่งค่าไปยัง views echo view('templates/header', $data); echo view('news/view', $data); echo view('templates/footer', $data); } }
สร้าง Views สำหรับแสดงผล
เมื่อเราสร้าง controller เพื่อจัดการข้อมูลผ่านการเรียกใช้งาน model และส่งค่ามายัง views เรียบร้อยแล้ว
ต่อไปก็ให้เราสร้างไฟล์ overview.php และ view.php เพื่อแสดงผลข้อมูล ดังนี้
app/Views/news/overview.php
<h2><?= esc($title); ?></h2> <!--ถ้ามีข้อมูล --> <?php if (! empty($news) && is_array($news)) : ?> <!-- วนลูปแสดงข้อมูล--> <?php foreach ($news as $news_item): ?> <h3><?= esc($news_item['title']); ?></h3> <div class="main"> <?= esc($news_item['body']); ?> </div> <p><a href="/news/<?= esc($news_item['slug'], 'url'); ?>">View article</a></p> <?php endforeach; ?> <!--ถ้าไม่มีข้อมูล--> <?php else : ?> <h3>No News</h3> <p>Unable to find any news for you.</p> <?php endif ?>
app/Views/news/view.php
<h2><?= esc($news['title']); ?></h2> <?= esc($news['body']); ?>
อย่างที่อธิบายไปบ้างแล้วไฟล์ overview.php เป็นการแสดงรายการข่าวทั้งหมด ส่วนไฟล์ view.php เป็นการแสดง
เฉพาะข่าวจากรายการที่เลือก โดยส่งค่า slug ไปค้นหาในตาราง news
ตอนนี้เราใกล้จะสำเร็จในขั้นตอนการแสดงข้อมูลแบบ dynamic แล้ว แต่จะขาดส่วนต่อไปนี้ไม่ได้
นั่นก็คือการกำหนด routing หรือการจัดการ rule ให้กับ url ที่เรียกใช้งาน ซึ่งเป็นขั้นตอนสุดท้าย
การกำหนด Routing
เนื่องจากรูปแบบ rules ที่เรากำหนดก่อนหน้ามีความจำกัดขอบเขตค่อนข้างกว้าง
app/Config/Routes.pbp
$routes->get('/', 'Home::index'); $routes->get('(:any)', 'Pages::view/$1');
สังเกตว่า หลัง "/" หากเป็นข้อความใดๆ จะไปเรียกไปที่ Pages controller เพราะเรากำหนดโดยใช้รูปแบบ
เป็น (:any)
เนื่องจากเราต้องการให้การเรียกใช้งาน dynamic page เกี่ยวกับข่าวของเรา เรียกใช้งานผ่าน url ในรุปแบบ
/news - สำหรับข่าวสารรวม /news/xxxx - สำหรับหน้าข่าวสารเฉพาะรายการ
ดังนั้นเราต้องเพิ่ม rule ใหม่ไว้ก่อนเงื่อนไข '(:any)' เพื่อให้ rule ของเราทำงาน ก็จะได้เป็นดังนี้
app/Config/Routes.pbp
/** * -------------------------------------------------------------------- * Route Definitions * -------------------------------------------------------------------- */ // We get a performance increase by specifying the default // route since we don't have to scan directories. $routes->get('/', 'Home::index'); $routes->get('news/(:segment)', 'News::view/$1'); $routes->get('news', 'News::index'); $routes->get('(:any)', 'Pages::view/$1');
ในการกำหนดการเรียกใช้งาน News controller สังเกตว่า เรายังมีการกำหนดลำดับของ url ที่มีจำนวน segment
มากๆ ไว้ด้านบน ดังกรณีนี้ url ในส่วนของ news เราจะพิจารณากรณีที่มีค่า slug ส่งค่ามาก่อน ถ้าไม่มี เราก็มาดูต่อ
ในบรรทัดถัดไป เพราะสมมติ ถ้าเราสลับให้เป็นดังนี้
$routes->get('news', 'News::index'); $routes->get('news/(:segment)', 'News::view/$1');
ถ้าเราเรียกไปยัง url ของรายการข่าวรายการใดๆ ก็จะเข้าเงื่อนไข news ตัวแรก เพราะพิจารณาเฉพาะตัวแรกก่อน
นั่นคือ /home/xxx ก็เข้าเงื่อนไขแรก
ดังนั้น พอเข้าเงื่อนไขตัวแรก แทนที่จะเป็นเงื่อนไขที่สอง ซึ่งเป็นเงื่อนไขที่ถูกต้อง ก็ทำให้การทำงานผิดพลาดได้
ข้อสังเกตอีกอย่าง ให้เราดูในส่วนของ :segment กับ :any ทำไมเราถึงใช้ต่างกัน ทั้งสองค่านี้เปรียบเสมือน ตัวที่
กำหนดไว้สำหรับใช้แทนที่ค่าอื่นในอนาคตหรือที่เรียกว่า placeholder ความแตกต่างทั้งสอง ค่านี้คือ
:segment จะเป็นค่าใดก็ได้ แต่ห้ามเป็น / ตัวอย่างเช่น
xxx-xxx, xxx.xxx เหล่านี้ได้ แต่ไม่สามารถเป็น xxx/xxxx หรือ xxx/xxx/xxx แบบนี้ไม่ได้
นั่นคือเงื่อนไขของ segment คือค่าที่ไม่ทำให้ค่านั้น เหมือนเป็น directory ย่อย หรือ path ย่อย นั่นเอง
:any จะเป็นค่าใดๆ ก็ได้รวมถึง xxx/xxx ได้ด้วย
เนื้อหาเกี่ยวกับ placeholder อาจจะดูเพิ่มเดิมเกี่ยวกับ routing ภายหลัง
ตอนนี้เป็นอันเสร็จเรียบร้อยสำหรับการกำหนดค่าต่างๆ เราพร้อมทดสอบการทำงาน ได้เมื่อเรียกไปยัง
https://www.mysslweb.com/news
ก็จะได้ผลลัพธ์ดังนี้
และเมื่อคลิกไปยังรายการที่ต้องการดู เช่น
https://www.mysslweb.com/news/say-it-isnt-so
slug ที่มีค่าเท่ากับค่า "say-it-isnt-so" ก็จะถูกส่งไปดึงข้อมูลที่ตรงมาแสดง ดังรูป
ตอนนี้เราเริ่มเห็นความเปลี่ยนแปลง หลายๆ อย่างของ CI4 ที่แตกต่างจาก CI3 มากขึ้น
ตอนหน้าจะเป็นอะไรรอติดตาม