Using DRY code to render blocks for server (PHP) and client (JS) sides
Dynamic (Gutenberg) blocks are blocks that build their structure and content on the fly when the block is rendered on the front end.
Rendering a dynamic block in the front-end (to display it in the WordPress editor) and in the server-side (to generate the HTML for the blog post) will typically fetch its data in two different ways:
- Connecting to the API on the client-side (JavaScript)
- Calling WordPress functions on the server-side (PHP)
With Gato GraphQL and extensions, it is possible to make this logic DRY, having a single source of truth to fetch data for both the client and server-sides. Let's explore how to do this.
.gql
files Storing GraphQL queries in
In order to connect to the GraphQL server from the client, we typically execute the GraphQL query embedded within the JavaScript code, like this:
const response = await fetch(endpoint, {
body: JSON.stringify({
query: `
query {
posts {
id
title
author {
id
name
}
}
}
`
)
} );
We can alternatively store the GraphQL query in a .gql
(or .graphql
) file, and import its contents using Webpack's raw-loader
:
// File webpack.config.js
const config = require( '@wordpress/scripts/config/webpack.config' );
config.module.rules.push(
{
test: /\.(gql|graphql)$/i,
use: 'raw-loader',
},
);
module.exports = config;
(This code works for Webpack v4; for v5, we must use Asset Modules instead.)
Next, we place the GraphQL query inside a .gql
file:
# File graphql-documents/fetch-posts-with-author.gql
query {
posts {
id
title
author {
id
name
}
}
}
Finally, within the block's code, we import the file and pass its contents to fetch
:
import graphQLQuery from './graphql-documents/fetch-posts-with-author.gql';
// ...
const response = await fetch(endpoint, {
body: JSON.stringify({
query: graphQLQuery
)
} );
.gql
files in the server-side Resolving
The GraphQL file we created above will be our single source of truth to fetch data for the block. It already satisfies this for the client-side; let's now see do it for the server-side.
The Internal GraphQL Server extension installs a server that can be invoked within our application, using PHP code.
the Internal GraphQL Server provides the following static methods, via class GraphQLServer
:
executeQuery
: Execute a GraphQL queryexecuteQueryInFile
: Execute a GraphQL query contained in a (.gql
) fileexecutePersistedQuery
: Execute a persisted GraphQL query (providing its ID as an int, or slug as a string) (the Persisted Queries extension is required)
The signature of executeQueryInFile
looks like this:
namespace GatoGraphQL\InternalGraphQLServer;
class GraphQLServer {
/**
* Execute a GraphQL query contained in a (`.gql`) file
*/
public static function executeQueryInFile(
string $file,
array $variables = [],
?string $operationName = null
): Response {
// ...
}
}
By invoking executeQueryInFile
passing the .gql
file created earlier on, we retrieve the data when rendering the dynamic block:
use GatoGraphQL\InternalGraphQLServer\GraphQLServer;
$block = [
'render_callback' => function(array $attributes, string $content): string {
// Provide the GraphQL query file
$file = __DIR__ . '/blocks/my-block/graphql-documents/fetch-posts-with-author.gql';
// Execute the query against the internal server
$response = GraphQLServer::executeQueryInFile($file);
// Get the content and decode it
$responseContent = json_decode($response->getContent(), true);
// Access the data and errors from the response
$data = $responseContent["data"] ?? [];
$errors = $responseContent["errors"] ?? [];
// Do something with the data
// $content = $this->useGraphQLData($content, $data, $errors);
// ...
return $content;
},
];
register_block_type("namespace/my-block", $block);
This way, a single .gql
file retrieves the data to power blocks on both the client and server-sides.