How to build custom Timber Twig templating engines extensions utilizing modern WP HTTP API schemas
Leveraging the WP_HTTP API for Advanced Timber Twig Extensions
For enterprise-grade WordPress applications, extending the Timber Twig templating engine with custom functionalities is a common requirement. Often, these extensions need to interact with external services or fetch data dynamically. The WordPress HTTP API (`WP_HTTP`) provides a robust, standardized, and secure mechanism for making these outbound requests. This post details how to build custom Twig extensions that harness the `WP_HTTP` API, focusing on practical implementation patterns and considerations for production environments.
Registering Custom Twig Functions with Timber
Timber allows for the registration of custom Twig functions, filters, and global variables. To integrate `WP_HTTP` calls, we’ll define a custom Twig function that acts as a wrapper. This function will accept parameters for the URL, request method, headers, and body, and return the fetched data, typically in JSON format.
The registration process typically occurs within your theme’s `functions.php` file or, more appropriately for plugin development, within a plugin’s main file or an included service provider.
Example: Registering a `remote_get_json` Twig Function
This example demonstrates registering a function named `remote_get_json` that utilizes `wp_remote_get` to fetch JSON data from a specified URL.
<?php
/**
* Plugin Name: Custom Timber HTTP Extensions
* Description: Adds custom Twig functions for making HTTP requests.
* Version: 1.0
* Author: Antigravity
*/
use Timber\Twig_Function;
add_filter( 'timber/twig/functions', function( array $functions ) {
$functions[] = new Twig_Function( 'remote_get_json', 'custom_timber_http_remote_get_json' );
return $functions;
} );
/**
* Fetches JSON data from a remote URL using WP_HTTP API.
*
* @param string $url The URL to fetch data from.
* @param array $args Optional. Arguments to pass to wp_remote_get.
* See https://developer.wordpress.org/reference/functions/wp_remote_get/
* @return array|WP_Error Decoded JSON data or a WP_Error object on failure.
*/
function custom_timber_http_remote_get_json( string $url, array $args = [] ): array | \WP_Error {
$response = wp_remote_get( $url, $args );
if ( is_wp_error( $response ) ) {
return $response; // Return the WP_Error object directly
}
$body = wp_remote_retrieve_body( $response );
$data = json_decode( $body, true );
if ( json_last_error() !== JSON_ERROR_NONE ) {
return new \WP_Error( 'json_decode_error', 'Failed to decode JSON response.' );
}
return $data;
}
?>
In this code:
- We hook into the
timber/twig/functionsfilter to add our custom function. - The
remote_get_jsonfunction is defined, accepting a URL and optional arguments forwp_remote_get. - It uses
wp_remote_getto make the request. - Error handling for
WP_Errorobjects is crucial. - The response body is retrieved and decoded using
json_decode. - A check for JSON decoding errors is included.
- The decoded data (as a PHP associative array) or a
WP_Errorobject is returned.
Utilizing the Custom Twig Function in Templates
Once registered, the remote_get_json function can be called directly within your Twig templates. This allows for dynamic content injection from external APIs.
{# Example: Fetching posts from an external WordPress REST API #}
{% set external_posts = remote_get_json('https://example.com/wp-json/wp/v2/posts', { 'headers': {'Accept': 'application/json'} }) %}
{% if external_posts is not instance of('WP_Error') %}
{% if external_posts %}
<h2>Latest Posts from External Site</h2>
<ul>
{% for post in external_posts %}
<li><a href="{{ post.link }}">{{ post.title.rendered }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No external posts found.</p>
{% endif %}
{% else %}
<p>Error fetching external posts: {{ external_posts.get_error_message() }}</p>
{% endif %}
In this Twig snippet:
- We call
remote_get_jsonwith the target API endpoint. - We pass an optional
headersargument to ensure the API returns JSON. - Crucially, we check if the result is a
WP_Errorobject before attempting to iterate over the data. This is essential for robust error handling. - If successful, we loop through the fetched posts and display their titles and links.
Advanced Scenarios: POST Requests and Authentication
The `WP_HTTP` API supports various request methods, including POST, PUT, DELETE, and more. You can create custom Twig functions for these as well, passing necessary data and authentication headers.
Example: `remote_post_json` Function
add_filter( 'timber/twig/functions', function( array $functions ) {
$functions[] = new Twig_Function( 'remote_post_json', 'custom_timber_http_remote_post_json' );
return $functions;
} );
/**
* Sends a POST request with JSON data to a remote URL using WP_HTTP API.
*
* @param string $url The URL to send the POST request to.
* @param array $body The data to send in the request body (will be JSON encoded).
* @param array $args Optional. Additional arguments to pass to wp_remote_post.
* See https://developer.wordpress.org/reference/functions/wp_remote_post/
* @return array|WP_Error Decoded JSON response data or a WP_Error object on failure.
*/
function custom_timber_http_remote_post_json( string $url, array $body, array $args = [] ): array | \WP_Error {
$default_args = [
'headers' => [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'body' => json_encode( $body ),
];
$merged_args = array_merge_recursive( $default_args, $args );
$response = wp_remote_post( $url, $merged_args );
if ( is_wp_error( $response ) ) {
return $response;
}
$body = wp_remote_retrieve_body( $response );
$data = json_decode( $body, true );
if ( json_last_error() !== JSON_ERROR_NONE ) {
return new \WP_Error( 'json_decode_error', 'Failed to decode JSON response.' );
}
return $data;
}
When using this function in Twig, you would pass the data to be sent as the second argument:
{# Example: Submitting a form to an external API #}
{% set submission_data = {
'name': 'John Doe',
'email': '[email protected]',
'message': 'Hello from Timber!'
} %}
{% set api_response = remote_post_json('https://api.external-service.com/submit', submission_data) %}
{% if api_response is not instance of('WP_Error') %}
<p>Submission successful: {{ api_response.message }}</p>
{% else %}
<p>Submission failed: {{ api_response.get_error_message() }}</p>
{% endif %}
Security and Performance Considerations
When building such extensions, security and performance are paramount:
Implementing Caching with Transients API
Here’s an enhanced version of `remote_get_json` that incorporates caching using the WordPress Transients API:
add_filter( 'timber/twig/functions', function( array $functions ) {
$functions[] = new Twig_Function( 'remote_get_json_cached', 'custom_timber_http_remote_get_json_cached' );
return $functions;
} );
/**
* Fetches JSON data from a remote URL using WP_HTTP API with caching.
*
* @param string $url The URL to fetch data from.
* @param int $cache_duration_seconds Duration in seconds to cache the response.
* @param array $http_args Optional. Arguments to pass to wp_remote_get.
* @return array|WP_Error Decoded JSON data or a WP_Error object on failure.
*/
function custom_timber_http_remote_get_json_cached( string $url, int $cache_duration_seconds = HOUR_IN_SECONDS, array $http_args = [] ): array | \WP_Error {
// Generate a unique cache key based on URL and potentially important args
$cache_key = 'remote_json_' . md5( $url . serialize( $http_args ) );
$cached_data = get_transient( $cache_key );
if ( false !== $cached_data ) {
// Return cached data if available and valid
return $cached_data;
}
$response = wp_remote_get( $url, $http_args );
if ( is_wp_error( $response ) ) {
return $response;
}
$body = wp_remote_retrieve_body( $response );
$data = json_decode( $body, true );
if ( json_last_error() !== JSON_ERROR_NONE ) {
return new \WP_Error( 'json_decode_error', 'Failed to decode JSON response.' );
}
// Store the fetched data in transient cache
set_transient( $cache_key, $data, $cache_duration_seconds );
return $data;
}
Usage in Twig:
{# Fetch posts, cache for 1 hour #}
{% set external_posts = remote_get_json_cached('https://example.com/wp-json/wp/v2/posts', 3600, { 'headers': {'Accept': 'application/json'} }) %}
{# ... rest of the template logic ... #}
Conclusion
By creating custom Twig functions that wrap the `WP_HTTP` API, you can seamlessly integrate dynamic external data into your Timber-powered WordPress sites. This approach offers a clean separation of concerns, enhances template readability, and leverages WordPress’s robust HTTP handling capabilities. Always prioritize security, performance, and thorough error handling in production deployments.