Использование принципа DRY для рендеринга блоков на стороне сервера (PHP) и клиента (JS)
Динамические блоки (Gutenberg) — это блоки, которые строят свою структуру и содержимое «на лету» в момент отображения на фронтенде.
Рендеринг динамического блока на фронтенде (для отображения в редакторе WordPress) и на стороне сервера (для генерации HTML записи блога) обычно предполагает получение данных двумя разными способами:
- Обращение к API на стороне клиента (JavaScript)
- Вызов функций WordPress на стороне сервера (PHP)
С помощью Gato GraphQL и расширений эту логику можно сделать DRY, имея единый источник истины для получения данных как на стороне клиента, так и на стороне сервера. Рассмотрим, как это сделать.
Хранение GraphQL queries в файлах .gql
Для подключения к серверу GraphQL со стороны клиента мы обычно выполняем GraphQL query, встроенный непосредственно в код JavaScript, вот так:
const response = await fetch(endpoint, {
body: JSON.stringify({
query: `
query {
posts {
id
title
author {
id
name
}
}
}
`
)
} );Альтернативный подход — сохранить GraphQL query в файл .gql (или .graphql) и импортировать его содержимое с помощью raw-loader для Webpack:
// 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;(Этот код работает для Webpack v4; для v5 вместо него необходимо использовать Asset Modules.)
Далее помещаем GraphQL query в файл .gql:
# File graphql-documents/fetch-posts-with-author.gql
query {
posts {
id
title
author {
id
name
}
}
}Наконец, в коде блока импортируем файл и передаём его содержимое в fetch:
import graphQLQuery from './graphql-documents/fetch-posts-with-author.gql';
// ...
const response = await fetch(endpoint, {
body: JSON.stringify({
query: graphQLQuery
)
} );Использование файлов .gql на стороне сервера
Созданный выше файл GraphQL станет единым источником истины для получения данных блока. Для клиентской стороны задача уже решена; теперь посмотрим, как реализовать то же самое на стороне сервера.
Расширение Internal GraphQL Server устанавливает сервер, который можно вызывать внутри нашего приложения с помощью PHP-кода.
Internal GraphQL Server предоставляет следующие статические методы через класс GraphQLServer:
executeQuery: выполняет GraphQL queryexecuteQueryInFile: выполняет GraphQL query, содержащийся в файле (.gql)executePersistedQuery: выполняет сохранённый GraphQL query (передавая его ID в виде int или slug в виде string) (требуется расширение Persisted Queries)
Сигнатура метода executeQueryInFile выглядит следующим образом:
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 {
// ...
}
}Вызывая executeQueryInFile с ранее созданным файлом .gql, мы получаем данные при рендеринге динамического блока:
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);Таким образом, единственный файл .gql обеспечивает получение данных для блоков как на стороне клиента, так и на стороне сервера.