เขียนธีม WordPress ด้วย Sage 9 – ตอนที่ 4 ควบคุมข้อมูลด้วย Controller | WP63

เขียนธีม WordPress ด้วย Sage 9 – ตอนที่ 4 ควบคุมข้อมูลด้วย Controller

จากตอนที่ผ่านๆ มา  เราได้กล่าวถึง 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

ภายในคอนโทรลเลอร์นั้นมีส่วนที่ต้องการดังนี้

  1. ต้องระบุ namespace เป็น App\Controllers 
  2. เรียกใช้คลาส (use) Sober\Controller\Controller
  3. ชื่อคลาสเป็น CamelCase ของชื่อไฟล์เท็มเพลต (ชื่อเดียวกับไฟล์คอนโทรลเลอร์)
  4. คลาสต้องขยาย (extends) จากคลาส Controller

ตัวอย่างดังนี้

<?php
namespace App\Controllers;

use Sober\Controller\Controller;

class Single extends Controller {
	// ...
}

ภายในคลาสเราจะใช้เขียนเมธอดอยู่หลักๆ 3 ประเภทด้วยกัน  คือ

  1. public function MethodName() สำหรับ expose ตัวแปรออกไปยังเท็มเพลตโดยตรง (return $value) โดยค่าในเมธอดนี้จะไปโผล่ในเท็มเพลตผ่านตัวแปรชื่อเดียวกันแต่เป็น snake_case เช่นเมธอดเราชื่อว่า MethodName() เราก็จะเรียกค่าของเมธอดนี้ผ่านตัวแปร $method_name
  2. public static function MethodName() การทำงานคล้ายๆ กับแบบแรก  แต่ในกรณีนี้เมธอดจะไม่ถูกแปลงเป็นตัวแปร  เราสามารถเรียกใช้งานได้ผ่าน ClassName::MethodName() ได้ตามปกติ (วิธีนี้เราสามารถส่งตัวแปรเข้าไปให้ประมวลผลได้ด้วย)
  3. 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 นั้นครบเครื่องมากกว่านั่นเอง

สำหรับใครที่ยังไม่ได้ตอนก่อนหน้านี้  สามารถตามลิงก์ด้านล่างไปอ่านกันได้เลย

  1. เขียนธีม WordPress บนเทคโนโลยีสมัยใหม่ด้วย Sage 9 – ตอนที่ 1 แนะนำ Sage 9
  2. เขียนธีม WordPress ด้วย Sage 9 – ตอนที่ 2 ระบบจัดการแพ็คเกจ
  3. เขียนธีม WordPress ด้วย Sage 9 – ตอนที่ 3 Blade Template Engine

Posted

in

by

Comments

2 responses to “เขียนธีม WordPress ด้วย Sage 9 – ตอนที่ 4 ควบคุมข้อมูลด้วย Controller”

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.