WordPress Block Theme คืออะไรและ Pub/Sub Architecture
WordPress Block Theme เป็น theme generation ใหม่ที่ใช้ Full Site Editing (FSE) ทำให้สามารถแก้ไขทุกส่วนของ website ผ่าน block editor ได้ ไม่ต้องเขียน PHP template files แบบ classic themes อีกต่อไป Block Theme ใช้ HTML templates, theme.json สำหรับ styling และ block patterns สำหรับ layouts
Pub/Sub (Publish/Subscribe) Architecture เป็น messaging pattern ที่ publishers ส่ง messages ไปยัง channels โดยไม่ต้องรู้ว่าใครจะรับ และ subscribers รับ messages จาก channels ที่สนใจโดยไม่ต้องรู้ว่าใครส่ง ทำให้ components แยกกัน (decoupled) ได้อย่างดี
การรวม Block Theme กับ Pub/Sub Architecture ทำให้ WordPress site สามารถจัดการ events แบบ asynchronous ได้ เช่น เมื่อ user publish post ให้แจ้ง subscribers, เมื่อมี comment ใหม่ให้ส่ง notification, เมื่อ WooCommerce order เข้าให้ trigger workflow อัตโนมัติ
WordPress มี hooks system (actions และ filters) ที่เป็น Pub/Sub pattern อยู่แล้ว แต่ทำงานแบบ synchronous ภายใน PHP process เดียว การเพิ่ม external Pub/Sub เช่น Redis, RabbitMQ ทำให้รองรับ cross-service communication และ async processing ได้
สร้าง Block Theme ด้วย Full Site Editing
โครงสร้างและ configuration ของ Block Theme
# โครงสร้าง Block Theme
# my-block-theme/
# ├── style.css # Theme metadata
# ├── theme.json # Global styles & settings
# ├── templates/
# │ ├── index.html # Main template
# │ ├── single.html # Single post
# │ ├── page.html # Page template
# │ ├── archive.html # Archive template
# │ ├── 404.html # 404 page
# │ └── search.html # Search results
# ├── parts/
# │ ├── header.html # Header template part
# │ ├── footer.html # Footer template part
# │ └── sidebar.html # Sidebar template part
# ├── patterns/
# │ ├── hero.php # Hero section pattern
# │ └── cta.php # Call to action pattern
# ├── assets/
# │ ├── css/
# │ └── js/
# └── functions.php # Theme functions
# theme.json — Global Styles Configuration
# {
# "$schema": "https://schemas.wp.org/trunk/theme.json",
# "version": 2,
# "settings": {
# "color": {
# "palette": [
# {"slug": "primary", "color": "#0073aa", "name": "Primary"},
# {"slug": "secondary", "color": "#23282d", "name": "Secondary"},
# {"slug": "accent", "color": "#00a0d2", "name": "Accent"}
# ]
# },
# "typography": {
# "fontFamilies": [
# {
# "fontFamily": "Inter, sans-serif",
# "slug": "inter",
# "name": "Inter",
# "fontFace": [
# {"fontFamily": "Inter", "fontWeight": "400", "src": ["file:./assets/fonts/inter-regular.woff2"]},
# {"fontFamily": "Inter", "fontWeight": "700", "src": ["file:./assets/fonts/inter-bold.woff2"]}
# ]
# }
# ],
# "fontSizes": [
# {"slug": "small", "size": "0.875rem", "name": "Small"},
# {"slug": "medium", "size": "1rem", "name": "Medium"},
# {"slug": "large", "size": "1.5rem", "name": "Large"},
# {"slug": "x-large", "size": "2.25rem", "name": "Extra Large"}
# ]
# },
# "layout": {
# "contentSize": "720px",
# "wideSize": "1200px"
# }
# },
# "styles": {
# "blocks": {
# "core/button": {
# "border": {"radius": "4px"},
# "color": {"background": "var(--wp--preset--color--primary)"}
# }
# }
# }
# }
# templates/single.html
#
#
#
#
#
#
#
#
#
#
ออกแบบ Pub/Sub System สำหรับ WordPress Events
สถาปัตยกรรม Pub/Sub สำหรับ WordPress
<?php
/**
* WordPress Event Bus — Pub/Sub Architecture
* Plugin: wp-event-bus
*/
// === Event Bus Core ===
class WP_Event_Bus {
private static $instance = null;
private $subscribers = [];
private $redis = null;
public static function get_instance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
$this->init_redis();
$this->register_wordpress_hooks();
}
private function init_redis() {
try {
$this->redis = new Redis();
$this->redis->connect(
defined('REDIS_HOST') ? REDIS_HOST : '127.0.0.1',
defined('REDIS_PORT') ? REDIS_PORT : 6379
);
if (defined('REDIS_PASSWORD') && REDIS_PASSWORD) {
$this->redis->auth(REDIS_PASSWORD);
}
} catch (Exception $e) {
error_log('Event Bus: Redis connection failed — ' . $e->getMessage());
$this->redis = null;
}
}
// Publish event
public function publish($channel, $event_data) {
$message = json_encode([
'channel' => $channel,
'data' => $event_data,
'timestamp' => current_time('c'),
'site_url' => get_site_url(),
]);
// Local subscribers
if (isset($this->subscribers[$channel])) {
foreach ($this->subscribers[$channel] as $callback) {
try {
call_user_func($callback, $event_data);
} catch (Exception $e) {
error_log("Event Bus callback error: " . $e->getMessage());
}
}
}
// Redis Pub/Sub
if ($this->redis) {
$this->redis->publish("wp_events:{$channel}", $message);
$this->redis->lPush("wp_events_log:{$channel}", $message);
$this->redis->lTrim("wp_events_log:{$channel}", 0, 999);
}
do_action('wp_event_bus_published', $channel, $event_data);
}
// Subscribe to channel
public function subscribe($channel, $callback) {
if (!isset($this->subscribers[$channel])) {
$this->subscribers[$channel] = [];
}
$this->subscribers[$channel][] = $callback;
}
// Register WordPress hooks as events
private function register_wordpress_hooks() {
// Post events
add_action('publish_post', function($post_id) {
$post = get_post($post_id);
$this->publish('post.published', [
'post_id' => $post_id,
'title' => $post->post_title,
'author' => get_the_author_meta('display_name', $post->post_author),
'permalink' => get_permalink($post_id),
]);
});
// Comment events
add_action('wp_insert_comment', function($id, $comment) {
$this->publish('comment.created', [
'comment_id' => $id,
'post_id' => $comment->comment_post_ID,
'author' => $comment->comment_author,
'content' => wp_trim_words($comment->comment_content, 20),
]);
}, 10, 2);
// User events
add_action('user_register', function($user_id) {
$user = get_userdata($user_id);
$this->publish('user.registered', [
'user_id' => $user_id,
'username' => $user->user_login,
'email' => $user->user_email,
]);
});
}
}
// Initialize
add_action('init', function() {
WP_Event_Bus::get_instance();
});
// Usage in other plugins:
// $bus = WP_Event_Bus::get_instance();
// $bus->subscribe('post.published', function($data) {
// // Send notification
// });
?>
สร้าง Event-Driven Plugin ด้วย PHP
สร้าง plugin ที่ตอบสนองต่อ events
<?php
/**
* Plugin Name: WP Notification Hub
* Description: Event-driven notification system using Pub/Sub
*/
class WP_Notification_Hub {
private $bus;
public function __construct() {
$this->bus = WP_Event_Bus::get_instance();
$this->register_subscribers();
}
private function register_subscribers() {
// เมื่อมี post ใหม่ ส่ง email ถึง subscribers
$this->bus->subscribe('post.published', [$this, 'notify_post_published']);
// เมื่อมี comment ใหม่ ส่ง notification ถึงผู้เขียน
$this->bus->subscribe('comment.created', [$this, 'notify_new_comment']);
// เมื่อมี user สมัครใหม่
$this->bus->subscribe('user.registered', [$this, 'handle_new_user']);
}
public function notify_post_published($data) {
$subscribers = get_users(['role' => 'subscriber', 'fields' => 'user_email']);
foreach ($subscribers as $email) {
wp_mail(
$email,
"New Post: {$data['title']}",
"A new article has been published by {$data['author']}.\n\n" .
"Read it here: {$data['permalink']}",
['Content-Type: text/plain; charset=UTF-8']
);
}
// Async: Queue สำหรับ social media sharing
$this->bus->publish('social.share_queue', [
'title' => $data['title'],
'permalink' => $data['permalink'],
'platforms' => ['twitter', 'facebook', 'linkedin'],
]);
error_log("Notification Hub: Post published — {$data['title']}");
}
public function notify_new_comment($data) {
$post = get_post($data['post_id']);
if (!$post) return;
$author_email = get_the_author_meta('user_email', $post->post_author);
wp_mail(
$author_email,
"New comment on: {$post->post_title}",
"{$data['author']} commented:\n\n{$data['content']}",
['Content-Type: text/plain; charset=UTF-8']
);
}
public function handle_new_user($data) {
// Welcome email
wp_mail(
$data['email'],
'Welcome to our community!',
"Hi {$data['username']},\n\nThank you for joining!",
['Content-Type: text/plain; charset=UTF-8']
);
// Notify admins
$this->bus->publish('admin.notification', [
'type' => 'new_user',
'message' => "New user registered: {$data['username']}",
]);
}
}
new WP_Notification_Hub();
?>
รวม WordPress กับ Redis Pub/Sub
ตั้งค่า Redis Pub/Sub สำหรับ cross-service communication
#!/usr/bin/env python3
# wp_event_worker.py — Redis Pub/Sub Worker for WordPress Events
import redis
import json
import logging
from datetime import datetime
logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s %(message)s')
logger = logging.getLogger('wp_worker')
class WPEventWorker:
def __init__(self, redis_host='127.0.0.1', redis_port=6379):
self.redis = redis.Redis(host=redis_host, port=redis_port, decode_responses=True)
self.pubsub = self.redis.pubsub()
self.handlers = {}
def register_handler(self, channel, handler):
self.handlers[channel] = handler
def start(self, channels=None):
if channels is None:
channels = ['wp_events:*']
self.pubsub.psubscribe(channels)
logger.info(f"Worker started, listening on: {channels}")
for message in self.pubsub.listen():
if message['type'] not in ('pmessage', 'message'):
continue
try:
channel = message.get('channel', '')
data = json.loads(message['data'])
event_channel = data.get('channel', '')
logger.info(f"Event received: {event_channel}")
handler = self.handlers.get(event_channel)
if handler:
handler(data['data'], data)
else:
logger.debug(f"No handler for: {event_channel}")
except json.JSONDecodeError:
logger.error(f"Invalid JSON: {message['data']}")
except Exception as e:
logger.error(f"Handler error: {e}")
# Event Handlers
def handle_post_published(data, meta):
logger.info(f"New post: {data['title']} by {data['author']}")
# ส่ง push notification
# sync กับ Elasticsearch
# update cache
# trigger CDN purge
def handle_comment_created(data, meta):
logger.info(f"New comment on post {data['post_id']} by {data['author']}")
# Sentiment analysis
# Spam detection
# Real-time dashboard update
def handle_user_registered(data, meta):
logger.info(f"New user: {data['username']}")
# CRM sync
# Welcome email sequence
# Analytics tracking
def handle_social_share(data, meta):
logger.info(f"Share to: {data['platforms']}")
# Share to Twitter API
# Share to Facebook API
# Share to LinkedIn API
# Start worker
if __name__ == '__main__':
worker = WPEventWorker()
worker.register_handler('post.published', handle_post_published)
worker.register_handler('comment.created', handle_comment_created)
worker.register_handler('user.registered', handle_user_registered)
worker.register_handler('social.share_queue', handle_social_share)
worker.start()
# Docker Compose สำหรับ setup
# docker-compose.yml:
# services:
# wordpress:
# image: wordpress:latest
# ports: ["8080:80"]
# environment:
# WORDPRESS_DB_HOST: db
# REDIS_HOST: redis
# volumes:
# - ./plugins:/var/www/html/wp-content/plugins
# - ./themes:/var/www/html/wp-content/themes
#
# redis:
# image: redis:7-alpine
# ports: ["6379:6379"]
#
# worker:
# build: ./worker
# depends_on: [redis]
# environment:
# REDIS_HOST: redis
#
# db:
# image: mariadb:10.11
# environment:
# MYSQL_ROOT_PASSWORD: rootpass
# MYSQL_DATABASE: wordpress
Real-time Notifications ด้วย WebSocket
สร้างระบบ real-time notifications สำหรับ WordPress
#!/usr/bin/env python3
# ws_server.py — WebSocket Server for WordPress Real-time Events
import asyncio
import json
import redis.asyncio as aioredis
import websockets
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger('ws_server')
connected_clients = {}
async def register(websocket, user_id=None):
connected_clients[websocket] = {'user_id': user_id, 'channels': set()}
logger.info(f"Client connected: {user_id} (total: {len(connected_clients)})")
async def unregister(websocket):
if websocket in connected_clients:
del connected_clients[websocket]
logger.info(f"Client disconnected (total: {len(connected_clients)})")
async def handler(websocket):
await register(websocket)
try:
async for message in websocket:
data = json.loads(message)
action = data.get('action')
if action == 'subscribe':
channel = data.get('channel')
connected_clients[websocket]['channels'].add(channel)
await websocket.send(json.dumps({'type': 'subscribed', 'channel': channel}))
elif action == 'auth':
connected_clients[websocket]['user_id'] = data.get('user_id')
except websockets.exceptions.ConnectionClosed:
pass
finally:
await unregister(websocket)
async def broadcast(channel, message):
for ws, info in connected_clients.items():
if channel in info['channels'] or '*' in info['channels']:
try:
await ws.send(json.dumps({
'type': 'event', 'channel': channel, 'data': message
}))
except websockets.exceptions.ConnectionClosed:
pass
async def redis_listener():
r = aioredis.from_url('redis://localhost:6379')
pubsub = r.pubsub()
await pubsub.psubscribe('wp_events:*')
async for message in pubsub.listen():
if message['type'] == 'pmessage':
try:
data = json.loads(message['data'])
channel = data.get('channel', '')
await broadcast(channel, data['data'])
except Exception as e:
logger.error(f"Redis listener error: {e}")
async def main():
async with websockets.serve(handler, "0.0.0.0", 8765):
logger.info("WebSocket server started on ws://0.0.0.0:8765")
await redis_listener()
if __name__ == '__main__':
asyncio.run(main())
# JavaScript Client (ฝั่ง WordPress theme)
# const ws = new WebSocket('ws://localhost:8765');
# ws.onopen = () => {
# ws.send(JSON.stringify({action: 'subscribe', channel: 'post.published'}));
# ws.send(JSON.stringify({action: 'subscribe', channel: 'comment.created'}));
# };
# ws.onmessage = (event) => {
# const msg = JSON.parse(event.data);
# if (msg.type === 'event') {
# showNotification(msg.channel, msg.data);
# }
# };
# function showNotification(channel, data) {
# const toast = document.createElement('div');
# toast.className = 'wp-toast-notification';
# toast.textContent = `New : `;
# document.body.appendChild(toast);
# setTimeout(() => toast.remove(), 5000);
# }
FAQ คำถามที่พบบ่อย
Q: Block Theme กับ Classic Theme ต่างกันอย่างไร?
A: Classic Theme ใช้ PHP template files (header.php, single.php, footer.php) และ functions.php สำหรับ customization Block Theme ใช้ HTML templates ที่มี block markup, theme.json สำหรับ styles และ Full Site Editing ที่แก้ไขได้ผ่าน GUI Block Theme เป็นทิศทางอนาคตของ WordPress ที่ลดการเขียน PHP code
Q: WordPress Hooks กับ External Pub/Sub ต่างกันอย่างไร?
A: WordPress Hooks (actions/filters) ทำงาน synchronous ภายใน PHP process เดียว ถ้า callback ช้าจะทำให้ page load ช้า External Pub/Sub (Redis, RabbitMQ) ทำงาน asynchronous ข้าม processes/services ได้ เหมาะสำหรับงานที่ใช้เวลานานเช่น sending emails, image processing, API calls ไม่กระทบ performance ของ WordPress
Q: Redis Pub/Sub เหมาะกับ WordPress ทุกกรณีไหม?
A: Redis Pub/Sub เหมาะสำหรับ real-time messaging ที่ไม่ต้องการ message persistence ถ้า subscriber offline จะ miss messages สำหรับงานที่ต้องการ guaranteed delivery แนะนำใช้ Redis Streams หรือ RabbitMQ แทน สำหรับ WordPress ขนาดเล็กอาจใช้ WP Cron + database queue ก็เพียงพอ
Q: WebSocket server ต้อง scale อย่างไร?
A: สำหรับ traffic สูง ใช้ Nginx เป็น WebSocket proxy หน้า WebSocket servers หลายตัว ใช้ Redis Pub/Sub เป็น message broker ระหว่าง servers ใช้ sticky sessions หรือ broadcast ผ่าน Redis ทุก server ได้รับ messages เหมือนกัน สำหรับ managed solution ใช้ Pusher, Ably หรือ AWS API Gateway WebSocket
