จากตอนที่ผ่านๆ มา เราได้กล่าวถึง Controller กันไปบ้างแล้ว โดยในแนวคิดการเขียนโปรแกรมแบบ MVC นั้น ส่วนของการแสดงผลและการประมวลผลจะแยกออกจากกันอย่างชัดเจน และจะช่วยให้เราสามารถนำเอาส่วนการแสดงผล (หรือ View) กลับมาใช้งานใหม่ได้สะดวกขึ้น
ให้ลองนึกตามว่าเรากำลังทำเว็บอยู่เว็บหนึ่ง โดยในเว็บจะมีอยู่ส่วนหนึ่งที่เป็นการดึงข้อมูลจาก 3 แหล่งข้อมูลมาแสดง (สมมุติว่าเป็น Post, ข้อมูลจาก API, และข้อมูลจาก Database ที่เราเพิ่มเอง) แต่ว่าในส่วนการแสดงผลนั้นจะใช้โครง HTML เหมือนกันทั้งหมด
ท่ามาตรฐานที่เราทำกันบ่อยๆ นั่นคือสร้างไฟล์ Partial Template ขึ้นมาไฟล์หนึ่ง จากนั้นเขียนโค้ดตรวจสอบว่าเท็มเพลตนี้ถูกเรียกจากตรงไหน แล้วค่อยดึงข้อมูลตามที่ต้องการออกมา หรือบางคนเถื่อนหน่อยก็อาจจะสร้างเท็มเพลตขึ้นมา 3 ไฟล์สำหรับแต่ละส่วนเลย แล้ว include เข้ามาเป็นส่วนๆ ไป ซึ่งแต่ละวิธีนั้นก็จะมีความยุ่งยากต่างกันออกไป
Controller พระเอกที่จะมาแก้ปัญหานี้
ตามหลักการของ MVC แล้ว View และ Controller จะแยกออกจากกันอย่างอิสระ View ไม่จำเป็นต้องรับรู้ว่ามันอยู่ตรงไหน ต้องไปดึงข้อมูลจากอะไร โดย View จะนอนเฉยๆ เป็นผักต้มรอให้ Controller ส่งข้อมูลมาให้เพียงอย่างเดียว
ดังนั้นจากโจทย์ด้านบน เราเพียงแค่เขียนเท็มเพลตที่แสดงค่าจากตัวแปรออกมาเท่านั้น และตัวแปรต่างๆ เหล่านั้นเราก็จะใช้ Controller ในการส่งค่าออกมา
การสร้าง Controller
ในระหว่างที่กำลังเขียนบทความตอนนี้อยู่ Roots ได้ปล่อย Sage 9.0.2 ออกมา ซึ่งมีจุดเปลี่ยนสำคัญคือการอัพเกรดไปใช้
soberwp/controller
เวอร์ชัน 2.1.0 แทน 9.0.0-beta4 ที่ใช้ในเวอร์ชันก่อนหน้านี้ ดังนั้นบทความนี้จะเขียนโดยอ้างอิงจากเวอร์ชัน 2.1.0 แทน
ในขั้นตอนนี้ให้เราเปิดไปยังไดเรคทอรี่ ./app/Controllers
และเราจะพบกับไฟล์คอนโทรลเลอร์อยู่ 2 ไฟล์อยู่แล้ว นั่นคือ App.php
และ FrontPage.php
App.php
เป็น Global Controller คือเท็มเพลตทุกไฟล์จะสามารถเข้าถึงเมธอดที่อยู่ในคอนโทรลเลอร์นี้ได้ทั้งหมด ในขณะที่ไฟล์คอนโทรลเลอร์อื่นนั้นจะเป็นการตั้งชื่อตามชื่อเท็มเพลตต่างๆ โดยเราจะต้องตั้งชื่อไฟล์ให้ตรงกับชื่อไฟล์เท็มเพลต และตั้งชื่อเป็น CamelCase แทน ตัวอย่างเช่น
FrontPage.php
สำหรับfront-page.blade.php
Single.php
สำหรับsingle.blade.php
SinglePostType.php
สำหรับsingle-post-type.blade.php
ดูชื่อไฟล์เท็มเพลตทั้งหมดได้ที่ WPHierarchy
ภายในคอนโทรลเลอร์นั้นมีส่วนที่ต้องการดังนี้
- ต้องระบุ
namespace
เป็นApp\Controllers
- เรียกใช้คลาส (
use
)Sober\Controller\Controller
- ชื่อคลาสเป็น CamelCase ของชื่อไฟล์เท็มเพลต (ชื่อเดียวกับไฟล์คอนโทรลเลอร์)
- คลาสต้องขยาย (
extends
) จากคลาสController
ตัวอย่างดังนี้
<?php namespace App\Controllers; use Sober\Controller\Controller; class Single extends Controller { // ... }
ภายในคลาสเราจะใช้เขียนเมธอดอยู่หลักๆ 3 ประเภทด้วยกัน คือ
public function MethodName()
สำหรับ expose ตัวแปรออกไปยังเท็มเพลตโดยตรง (return $value
) โดยค่าในเมธอดนี้จะไปโผล่ในเท็มเพลตผ่านตัวแปรชื่อเดียวกันแต่เป็น snake_case เช่นเมธอดเราชื่อว่าMethodName()
เราก็จะเรียกค่าของเมธอดนี้ผ่านตัวแปร$method_name
public static function MethodName()
การทำงานคล้ายๆ กับแบบแรก แต่ในกรณีนี้เมธอดจะไม่ถูกแปลงเป็นตัวแปร เราสามารถเรียกใช้งานได้ผ่านClassName::MethodName()
ได้ตามปกติ (วิธีนี้เราสามารถส่งตัวแปรเข้าไปให้ประมวลผลได้ด้วย)protected function MethodName()
เมธอดแบบ Protected จะไม่สามารถเข้าถึงได้ผ่านทางเท็มเพลต สามารถเรียกใช้ได้เฉพาะภายในคลาสเท่านั้น
หลักๆ เราจะใช้กันคือแบบแรก มาดูตัวอย่างกัน กับไฟล์ ./app/Controllers/Single.php
และ ./resources/views/single.blade.php
<?php // app/Controllers/Single.php namespace App\Controllers; use Sober\Controller\Controller; class Single extends Controller { public function PostTitle() { return get_the_title(); } }
{{-- resources/views/single.blade.php --}} <h3 class="entry__title"> {{ $post_title }} </h3>
สร้าง Component ด้วย Trait
ในธีมที่มีความซับซ้อนขึ้นมาระดับหนึ่ง คงหนีไม่พ้นการสร้าง Partial Templates สำหรับนำกลับมาใช้ซ้ำในส่วนต่างๆ (ตัวอย่างที่เห็นได้ชัดคือ header และ footer) และถ้าหาก Partial Templates เหล่านี้มีการเรียกใช้ค่าจากคอนโทรลเลอร์ การที่เราจะต้องไปนั่งเขียนเมธอดเดิมซ้ำๆ ในทุกคอนโทรลเลอร์คงไม่ใช่เรื่องดีเท่าไหร่นัก ซึ่งเราสามารถแก้ปัญหานี้ได้ด้วยการใช้ Trait
Trait เป็นฟีเจอร์ของ PHP ที่อนุญาตให้เราเขียนชุดเมธอดเก็บเอาไว้ และเอาไปใช้ซ้ำในหลายๆ คลาสได้ (คล้ายๆ กับการ include ไฟล์)
ตัวอย่างเช่นเราทำ Page Header แบบนี้แยกเอาไว้

สมมุติว่าชื่อเพจและคำอธิบายบน header นั้นสามารถดึงมาได้จากหลายที่แล้วแต่เงื่อนไข ดังนั้นเราเลยจะเขียนเมธอดสำหรับประมวลผลชื่อเพจและคำอธิบายนี้เอาไว้ (จะได้ไม่ต้องไปเขียนโค้ด if else ให้รกในไฟล์เท็มเพลต)
ในกรณีแบบนี้ แทนที่เราจะไปเขียนเมธอดเอาไว้ในทุกๆ คอนโทรลเลอร์ ให้เราสร้างไฟล์ Trait ขึ้นมาไฟล์หนึ่ง (แนะนำให้ตั้งชื่อไดเรคทอรี่และชื่อไฟล์ให้ตรงกับชื่อไฟล์เท็มเพลต เช่นไฟล์เพลตเก็บอยู่ที่ views/partials.page-header.blade.php
ก็ตั้งชื่อไฟล์ว่า Controllers/Partials/PageHeader.php
) และในไฟล์ในระบุ namespace
ประกาศ Trait และเขียนเมธอดให้เรียบร้อยดังในตัวอย่างนี้
<?php // app/Controllers/Partials/PageHeader.php namespace App\Controllers\Partials; trait PageHeader { public function PageTitle() { return $something; } public function PageDescription() { return $something; } }
และในไฟล์คอนโทรลเลอร์ก็เรียกใช้ Trait ที่สร้างไว้ดังนี้
<?php // app/Controllers/Single.php namespace App\Controllers; use Sober\Controller\Controller; class Single extends Controller { use App\Controllers\Partials\PageHeader; // เรียก Trait PageHeader ที่สร้างไว้ }
ส่งต่อเมธอดระหว่างคลาสด้วยอินเตอร์เฟซ Tree
หากเราย้อนกลับไปดู Template Hierarchy ของเวิร์ดเพรส จะพบว่าเท็มเพลตมีหาหาไฟล์เป็นชั้นไปเรื่อยๆ เช่นเท็มเพลตจะมองหาไฟล์ single-post-type.blade.php
ก่อน หากไม่เจอก็จะไปใช้ single.blade.php
แทน และถ้าหากยังไม่เจอ ก็จะถอยกลับไปถึง index.blade.php
ซึ่งตาม hierarchy นี้ คอนโทรลเลอร์จะมีอินเตอร์เฟซที่ชื่อว่า Tree
สำหรับให้เราเรียกใช้เมธอดที่อยู่ใน hierarchy ชั้นต่อไปได้ด้วย ตัวอย่างเช่นในคอนโทรลเลอร์ Index.php
เรามีเมธอด PageTitle()
อยู่ โดยปกติถ้าหากเราสร้างคอนโทรลเลอร์ Single.php
ขึ้นมา เราจะไม่สามารถเรียกใช้ PageTitle()
จากใน Index.php
ได้อีกต่อไป ทางเลือกของเราคือย้าย PageTitle()
ไปใส่ไว้ใน Trait แทน หรือไม่ก็เขียนเมธอด PageTitle()
ขึ้นมาใหม่ใน Single.php
อินเตอร์เฟซ Tree
จะเข้ามาช่วยแก้ปัญหานี้ โดยให้เราเรียกใช้โมดูล Sober\Controller\Module\Tree
และ implements
ลงไปในคลาส
<?php // app/Controllers/Index.php namespace App\Controllers; use Sober\Controller\Controller; class Index extends Controller { public function PageTitle() { return get_the_title(); } public function PageDate() { return get_time(); } }
<?php // app/Controllers/Single.php namespace App\Controllers; use Sober\Controller\Controller; use Sober\Controller\Module\Tree; // เรียกโมดูล Tree เข้ามา class Single extends Controller implements Tree { // อิมพลีเมนต์อินเตอร์เฟซ Tree เข้ามา }
จากตัวอย่างข้างต้น ในเท็มเพลต single.blade.php
จะสามารถเรียกใช้ $page_title
และ $page_date
ที่มาจาก Index.php
ได้ในทันที
วิธีนี้จะปลอดภัยกว่าการย้ายเมธอดไปเก็บไว้ใน Trait เพราะว่าเราสามารถประกาศเมธอดทับลงไปได้ด้วย เช่นหากเราต้องการเปลี่ยนข้อความชื่อเพจ ก็สามารถประกาศเมธอดทับได้เลย ดังนี้
<?php // app/Controllers/Single.php namespace App\Controllers; use Sober\Controller\Controller; use Sober\Controller\Module\Tree; class Single extends Controller implements Tree { public function PageTitle() { return "Page: " . get_the_title(); } }
จากตัวอย่างนี้ ในเท็มเพลตเราจะยังคงเรียกใช้ $page_date
ที่ได้มาจากจาก Index.php
ได้ตามปกติ แต่เมื่อเราเรียก $page_title
ก็จะเรียกใช้เมธอดนี้จาก Single.php
แทน
เมธอดที่จะสามารถส่งมาตามอินเตอร์เฟซได้ จะต้องเป็น
public
เท่านั้น
การ Override เท็มเพลต
มันจะมี edge case อยู่กรณีหนึ่ง นั่นคือเท็มเพลต 404.php
ที่เราไม่สามารถตั้งชื่อคลาสเป็นตัวเลขได้ ซึ่งคอนโทรลเลอร์ก็เปิดให้เรา override เท็มเพลตได้ผ่านพร็อพเพอร์ตี้ $template
โดยให้เราประกาศ protected $template = 'ชื่อเท็มเพลต'
ลงไปแบบนี้
<?php namespace App\Controllers; use Sober\Controller\Controller; class PageNotFound extends Controller { protected $template = "404"; }
ส่งท้าย
Controller น่าจะเป็นเรื่องสำคัญเรื่องสุดท้ายในซีรี่ส์บทความ Sage 9 แล้วครับ ซึ่งหลังจากนี้เราอาจจะมีบทความตอนพิเศษเกี่ยวกับ Sage 9 ออกมาให้อ่านกันบ้าง (ตอนนี้ที่วางแผนไว้คือเกี่ยวกับการเขียนวิดเจ็ดที่ใช้ Blade เป็นเท็มเพลต)
ก่อนหน้านี้ผมเจอคำถามที่น่าสนใจคำถามหนึ่งนั่นคือ Sage 9 เทียบกับ Timber ที่เป็นการนำเอา Twig มาใช้ในเวิร์ดเพรสนั้น อันไหนเข้าท่ากว่ากัน? จากที่ผมลองได้ศึกษาเพิ่มมานั้น Timber จะคล้ายคลึงกับการเขียนด้วย Sage 9 มากๆ โดยจะใช้ไฟล์เท็มเพลตเดิมของเวิร์ดเพรสทำหน้าที่เหมือนกับคอนโทรลเลอร์ จากนั้นเรียกไฟล์เท็มเพลต Twig เข้ามาแสดงผล (เอาจริงๆ คอนเส็ปท์น่าจะเข้าใจง่ายกว่า Sage 9 อยู่สักหน่อย) ทำให้ในจุดนี้ต่างกับ Sage 9 อยู่ไม่มากนัก
ส่วนที่ทำให้ Sage 9 ต่างกับ Timber จะเป็นในส่วนของ Workflow เสียมากกว่า โดยนอกจากเรื่องของ View/Contoller แล้ว Sage 9 ยังได้เตรียมเครื่องมือต่างๆ ไว้ให้อย่างครบครัน เช่นการจัดการแพ็คเกจด้วย Composer และ Yarn และมี Webpack สำหรับใช้คอมไพล์ resources ต่างๆ ทั้ง SCSS และ JS ซึ่งทำให้ Sage 9 นั้นครบเครื่องมากกว่านั่นเอง
สำหรับใครที่ยังไม่ได้ตอนก่อนหน้านี้ สามารถตามลิงก์ด้านล่างไปอ่านกันได้เลย
Leave a Reply