WordPress Development Recipe: Real-time custom event triggers using WebSockets and Block Patterns API
Leveraging WebSockets for Real-time WordPress Event Triggers
This recipe details a robust method for implementing real-time custom event triggers within WordPress, specifically targeting expert developers. We’ll combine the power of WebSockets for instantaneous communication with WordPress’s Block Patterns API for structured content generation. This approach is ideal for dynamic dashboards, collaborative editing features, or any scenario requiring immediate feedback to multiple connected clients without constant polling.
Prerequisites and Setup
Before diving into the code, ensure you have the following:
- A WordPress installation (local or remote).
- Composer installed for PHP dependency management.
- Node.js and npm/yarn for JavaScript development.
- A WebSocket server implementation. For this example, we’ll use Ratchet, a PHP WebSocket library.
First, let’s set up the Ratchet WebSocket server. Navigate to your WordPress root directory and create a new directory for your WebSocket server, e.g., ws-server. Inside this directory, initialize a Composer project:
cd ws-servercomposer init(accept defaults or configure as needed)composer require cboden/ratchet
Implementing the WebSocket Server
Create a file named server.php within the ws-server directory. This script will handle incoming WebSocket connections and broadcast messages.
clients = new \SplObjectStorage;
}
public function onOpen(ConnectionInterface $conn) {
// Store the new connection to send messages to later
$this->clients->attach($conn);
echo "New connection! ({$conn->resourceId})\n";
}
public function onMessage(ConnectionInterface $from, $msg) {
// In a real-world scenario, you'd parse $msg to determine event type and data.
// For this example, we'll assume messages are broadcast directly.
echo sprintf('Message received from %d: %s' . "\n", $from->resourceId, $msg);
// Broadcast the message to all other clients
foreach ($this->clients as $client) {
if ($from !== $client) {
// $client->send($msg); // Uncomment to broadcast to all *other* clients
$client->send($msg); // Broadcast to all clients, including sender
}
}
}
public function onClose(ConnectionInterface $conn) {
// The connection is closed, remove it, as we're not sending an open message
$this->clients->detach($conn);
echo "Connection {$conn->resourceId} has disconnected\n";
}
public function onError(ConnectionInterface $conn, \Exception $e) {
echo "An error has occurred: {$e->getMessage()}\n";
$conn->close();
}
/**
* Custom method to trigger an event and broadcast it.
* This method would be called from your WordPress plugin.
*/
public function triggerEvent(string $eventName, array $eventData) {
$message = json_encode(['event' => $eventName, 'data' => $eventData]);
echo "Triggering event: {$eventName} with data: " . json_encode($eventData) . "\n";
foreach ($this->clients as $client) {
$client->send($message);
}
}
}
// Create a new WebSocket server instance
$server = IoServer::factory(
new HttpServer(
new WsServer(
new EventBroadcaster()
)
),
8080 // Port to listen on
);
echo "WebSocket server started on port 8080...\n";
$server->run();
To run the server, execute the following command from the ws-server directory:
php server.php
Keep this terminal window open. The server will listen for connections on ws://localhost:8080.
WordPress Plugin Integration
Now, let’s create a WordPress plugin to interact with our WebSocket server. Create a new plugin directory, e.g., wp-content/plugins/realtime-events, and add a main plugin file, realtime-events.php.
sprintf( 'ws://%s:%d', WEBSOCKET_SERVER_HOST, WEBSOCKET_SERVER_PORT ),
)
);
// Enqueue Block Editor script for Block Patterns API integration
wp_enqueue_script(
'realtime-events-editor',
plugin_dir_url( __FILE__ ) . 'js/editor.js',
array( 'wp-blocks', 'wp-element', 'wp-editor', 'wp-components', 'wp-data' ),
'1.0',
true
);
// Localize script for editor-specific data if needed
// wp_localize_script('realtime-events-editor', 'editorConfig', array(...));
}
add_action( 'enqueue_block_editor_assets', 'realtime_events_enqueue_scripts' );
/**
* Register a custom block pattern.
*/
function realtime_events_register_block_pattern() {
if ( ! function_exists( 'register_block_pattern' ) ) {
return;
}
register_block_pattern(
'realtime-events/dynamic-message',
array(
'title' => __( 'Dynamic Realtime Message', 'realtime-events' ),
'description' => __( 'Displays a message updated in real-time via WebSocket.', 'realtime-events' ),
'categories' => array( 'realtime' ),
'content' => 'Waiting for real-time update...
',
'keywords' => array( 'realtime', 'dynamic', 'websocket', 'message' ),
)
);
}
add_action( 'init', 'realtime_events_register_block_pattern' );
/**
* Add a custom category for block patterns.
*/
function realtime_events_register_block_pattern_category( $categories ) {
$categories = array_merge( $categories, array(
array(
'name' => 'realtime',
'label' => __( 'Realtime', 'realtime-events' ),
),
) );
return $categories;
}
add_filter( 'block_pattern_categories', 'realtime_events_register_block_pattern_category', 10, 1 );
/**
* Example of triggering a WebSocket event from the backend.
* This could be hooked to any WordPress action or filter.
*/
function trigger_example_websocket_event() {
// In a real plugin, you'd need a way to access the running WebSocket server instance.
// This is a simplified example. A more robust solution might involve a REST API endpoint
// that communicates with the WebSocket server, or a persistent connection manager.
// For demonstration, we'll simulate sending a message.
// In a production environment, you'd likely have a dedicated service or
// a mechanism to communicate with your running WebSocket server process.
// Example: Hooking into post save
// add_action('save_post', 'trigger_websocket_on_post_save', 10, 3);
}
// add_action('plugins_loaded', 'trigger_example_websocket_event'); // Uncomment to run example setup
/**
* Example function to trigger WebSocket event on post save.
* This requires a mechanism to communicate with the running WebSocket server.
* For simplicity, this example assumes a hypothetical function `send_websocket_message`.
*/
function trigger_websocket_on_post_save( $post_id, $post, $update ) {
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
if ( wp_is_post_revision( $post_id ) ) {
return;
}
// Hypothetical function to send message to WebSocket server.
// In a real application, this would involve making an HTTP request to a
// dedicated API endpoint on your server that then forwards the message
// to the WebSocket server, or using a message queue.
// For this example, we'll just log it.
$message_data = array(
'post_id' => $post_id,
'title' => $post->post_title,
'status' => $post->post_status,
'event' => 'post_updated',
);
// Simulate sending the message. Replace with actual communication method.
error_log( 'Simulating WebSocket message send: ' . json_encode( $message_data ) );
// If you had a direct PHP connection to the WebSocket server (not recommended for production due to blocking):
/*
try {
$context = new ZMQContext();
$socket = $context->getTransport('tcp://127.0.0.1:5555'); // Example ZeroMQ port
$socket->send(json_encode($message_data));
} catch (Exception $e) {
error_log('Failed to send WebSocket message: ' . $e->getMessage());
}
*/
}
// add_action( 'save_post', 'trigger_websocket_on_post_save', 10, 3 );
?>
Client-Side JavaScript for WebSocket Connection
Create a js directory inside your plugin folder and add a file named client.js. This script will establish the WebSocket connection and handle incoming messages.
{
const websocketUrl = realtimeEventsConfig.websocketUrl;
let websocket;
function connectWebSocket() {
websocket = new WebSocket(websocketUrl);
websocket.onopen = () => {
console.log('WebSocket connection established.');
// Optionally send a message to the server upon connection
// websocket.send('Hello Server!');
};
websocket.onmessage = (event) => {
console.log('Message from server: ', event.data);
try {
const data = JSON.parse(event.data);
if (data.event === 'post_updated' && data.data && data.data.post_id) {
// Update a specific element, e.g., a paragraph with a real-time message
const messageDisplay = document.getElementById('realtime-message-display');
if (messageDisplay) {
messageDisplay.textContent = `Post ${data.data.post_id} (${data.data.title}) was updated. Status: ${data.data.status}`;
}
} else if (data.event && data.data) {
// Handle other custom events
console.log(`Received custom event: ${data.event}`, data.data);
// You could trigger custom DOM events or update other UI elements here
// Example: document.dispatchEvent(new CustomEvent(data.event, { detail: data.data }));
}
} catch (e) {
console.error('Failed to parse message or update UI:', e);
}
};
websocket.onclose = (event) => {
console.log('WebSocket connection closed:', event.code, event.reason);
// Attempt to reconnect after a delay
setTimeout(connectWebSocket, 5000); // Reconnect every 5 seconds
};
websocket.onerror = (error) => {
console.error('WebSocket error:', error);
websocket.close(); // Ensure close is called to trigger onclose
};
}
connectWebSocket();
});
Block Editor Integration with Block Patterns API
The Block Patterns API allows us to pre-define reusable content structures. We’ve already registered a basic pattern in realtime-events.php. The JavaScript in client.js targets an element within this pattern (#realtime-message-display) to update it dynamically.
For more advanced dynamic content within blocks, you would typically use dynamic blocks or server-side rendering. However, for real-time updates driven by external events, client-side JavaScript is the most efficient. The Block Patterns API serves as the entry point for placing these dynamic elements into content.
Create a file named editor.js in your plugin’s js directory. This script can be used to register custom block styles, attributes, or even custom blocks that might interact with real-time data, though for this specific recipe, client.js handles the dynamic updates.
{
// Example: Registering a custom block style for paragraphs
wp.blocks.registerBlockStyle('core/paragraph', {
name: 'realtime-highlight',
label: 'Realtime Highlight',
});
// Example: Adding a custom attribute to a block (requires a custom block)
// wp.hooks.addFilter(
// 'blocks.registerBlockType',
// 'realtime-events/add-custom-attribute',
// (settings, name) => {
// if (name === 'core/paragraph') { // Apply to paragraphs
// return {
// ...settings,
// attributes: {
// ...settings.attributes,
// realtimeId: {
// type: 'string',
// default: '',
// },
// },
// };
// }
// return settings;
// }
// );
// Example: Modifying block save function to include custom attribute
// wp.hooks.addFilter(
// 'blocks.getSaveContent.extraProps',
// 'realtime-events/add-realtime-id-attribute',
// (extraProps, blockType, attributes) => {
// if (blockType.name === 'core/paragraph' && attributes.realtimeId) {
// return { ...extraProps, 'data-realtime-id': attributes.realtimeId };
// }
// return extraProps;
// }
// );
console.log('Realtime Events editor script loaded.');
});
Triggering Events from WordPress Backend
The most challenging part of integrating a separate WebSocket server with WordPress is reliably triggering events from the PHP backend. Since the WebSocket server runs as a separate process, direct PHP calls are not feasible without complex inter-process communication (IPC) mechanisms.
Here are common strategies:
- REST API Endpoint: Create a custom WordPress REST API endpoint. Your PHP code triggers an event by making an HTTP request (e.g., POST) to this endpoint. The endpoint handler then communicates with the WebSocket server. This is a clean, decoupled approach.
- Message Queue (e.g., Redis, RabbitMQ): Your WordPress plugin publishes messages to a queue. A separate worker process (or the WebSocket server itself, if it supports listening to queues) consumes these messages and broadcasts them via WebSockets. This is highly scalable and resilient.
- Direct IPC (Advanced): If your WebSocket server is running on the same machine and you control its startup, you might use mechanisms like ZeroMQ or sockets directly from PHP. This requires careful management of the WebSocket server process and is generally less robust for typical WordPress hosting environments.
For the realtime-events.php example, we’ve included a commented-out hook for save_post and a placeholder function trigger_websocket_on_post_save. To make this functional, you would implement one of the above strategies. For instance, using the REST API:
'POST',
'callback' => 'handle_rest_api_trigger',
'permission_callback' => '__return_true', // Adjust permissions as needed
) );
} );
function handle_rest_api_trigger( WP_REST_Request $request ) {
$event_name = $request->get_param( 'event' );
$event_data = $request->get_param( 'data' );
if ( ! $event_name || ! is_array( $event_data ) ) {
return new WP_Error( 'invalid_params', 'Event name and data array are required.', array( 'status' => 400 ) );
}
// --- Communication with WebSocket Server ---
// Option 1: HTTP request to a dedicated API on your WS server
// $ws_server_api_url = 'http://localhost:8081/broadcast'; // Assuming WS server has a broadcast API
// $response = wp_remote_post( $ws_server_api_url, array(
// 'body' => json_encode( array( 'event' => $event_name, 'data' => $event_data ) ),
// 'headers' => array( 'Content-Type' => 'application/json' ),
// ) );
// if ( is_wp_error( $response ) ) {
// return new WP_Error( 'ws_communication_error', 'Failed to communicate with WebSocket server.', array( 'status' => 500 ) );
// }
// --- End Option 1 ---
// Option 2: Direct socket connection (less recommended for general WP hosting)
// This requires the WebSocket server to be listening on a specific port for commands.
$socket = @fsockopen( WEBSOCKET_SERVER_HOST, WEBSOCKET_SERVER_PORT + 1 ); // Assuming a separate command port
if ( $socket ) {
$message = json_encode( array( 'event' => $event_name, 'data' => $event_data ) );
fwrite( $socket, $message . "\n" );
fclose( $socket );
} else {
// Fallback or error handling if direct connection fails
error_log( 'Failed to connect to WebSocket server command port.' );
// You might want to return an error or log this failure.
}
// --- End Option 2 ---
return new WP_REST_Response( array( 'message' => 'Event triggered successfully.' ), 200 );
}
// Example usage: Triggering from save_post hook
function trigger_websocket_on_post_save_via_rest( $post_id, $post, $update ) {
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return;
if ( wp_is_post_revision( $post_id ) ) return;
$message_data = array(
'post_id' => $post_id,
'title' => $post->post_post_title,
'status' => $post->post_status,
'event' => 'post_updated',
);
// Use wp_remote_post to call our custom REST API endpoint
$rest_url = rest_url( 'realtime-events/v1/trigger' );
$response = wp_remote_post( $rest_url, array(
'body' => json_encode( array( 'event' => 'post_updated', 'data' => $message_data ) ),
'headers' => array( 'Content-Type' => 'application/json' ),
) );
if ( is_wp_error( $response ) ) {
error_log( 'Failed to trigger WebSocket event via REST API: ' . $response->get_error_message() );
} else {
error_log( 'Successfully triggered WebSocket event via REST API. Response: ' . wp_remote_retrieve_body( $response ) );
}
}
add_action( 'save_post', 'trigger_websocket_on_post_save_via_rest', 10, 3 );
?>
Deployment Considerations
Running a persistent WebSocket server requires careful consideration for production environments:
- Process Management: Use tools like
systemd,supervisor, or Docker to ensure your WebSocket server process runs continuously and restarts automatically if it crashes. - Scalability: For high traffic, a single WebSocket server instance might not be sufficient. Consider load balancing WebSocket connections across multiple server instances. This often involves a reverse proxy (like Nginx or HAProxy) that supports WebSocket proxying.
- Security: Implement authentication and authorization for WebSocket connections. Ensure your server uses WSS (WebSocket Secure) over TLS/SSL.
- Error Handling and Logging: Robust logging on both the server and client sides is crucial for debugging.
Conclusion
By integrating WebSockets with WordPress’s Block Patterns API, you can create highly dynamic and responsive user experiences. This recipe provides a foundational structure for real-time event triggering, from setting up a PHP WebSocket server to client-side JavaScript handling and WordPress plugin integration. Remember to adapt the backend communication strategy to your specific hosting environment and scalability needs.