Для людей, которые работают с Headless WordPress, Gato GraphQL предлагает новую замечательную функцию — Headless WordPress без WordPress.
В этом посте объясняется все о ней, также описывается, как это вообще возможно, и показывается видео, демонстрирующее функцию.
Запуск Gato GraphQL как отдельного PHP-приложения
Gato GraphQL создан с применением автономных компонентов PHP, управляемых через Composer, таким образом, что все компоненты PHP, составляющие сервер GraphQL, не зависят от WordPress!
Таким образом, сервер GraphQL может работать как отдельное PHP-приложение, и вы можете включить его в любое PHP-приложение на основе WordPress или чего-то еще.
Если в каком-то случае вашему приложению не требуется доступ к данным WordPress, то, по крайней мере, вы готовы к работе для такого случая применения.
В этом видео демонстрируется такой случай — взаимодействие с API GitHub для загрузки/установки артефактов из действий GitHub во время разработки.
В видео запрос GraphQL выполняет HTTP-запрос, чтобы получить последние плагины Gato GraphQL, созданные в действиях GitHub, которые загружаются как артефакты при объединении запроса на включение.
URL-адреса артефактов из ответа GraphQL затем вводятся в WP-CLI, чтобы плагины автоматически устанавливались на локальный веб-сервер DEV для запуска тестов.
Подробнее — в последнем разделе.
В этом случае применения, поскольку доступа к данным WordPress вообще нет, сервер GraphQL уже может работать как автономное приложение PHP.
Если бы мне было нужно, я даже смог бы использовать его в своем рабочем процессе GitHub Actions!
Миграция безголового приложения WordPress
Посмотрим, как запускать данные WordPress без WordPress, когда вы получаете доступ к этим данным.
Схема GraphQL, предоставленная Gato GraphQL, содержит поля для извлечения данных WordPress: сообщений, пользователей, комментариев, тегов, категорий и т. д.
Код в PHP-резолверах, извлекающих данные WordPress, зависит от WordPress; этот код не может работать в приложении, отличном от WordPress.
Однако в Gato GraphQL каждый из этих резолверов написан при помощи двух пакетов. Это:
- «Ванильный» PHP, содержащий весь общий код.
- WordPress-специфичный код, содержащий фактические вызовы методов WordPress, удовлетворяющих резолверу.
Например, в этом запросе GraphQL:
{
posts {
id
title
}
}
…логика получения сообщений состоит из:
- Поле
Root.postsнаходится в общем пакетеposts. - Его разрешение для WordPress при помощи метода
get_posts— он находится в специфичном для WordPress пакетеposts-wp.
Распределение кода между пакетами, отличными от WordPress, и пакетами WordPress составляет около 80/20 %. Это означает, что 80 % кода можно повторно использовать с другой платформой/CMS и только 20 % кода будет необходимо переопределить.
Более того, вся функциональность Gato GraphQL реализована через модули, и модули можно включать и выключать по желанию.

Модули — это функция, реализованная в целях безопасности: если вам не нужно раскрывать пользовательские данные в общедоступном API, можно отключить модуль Users — и соответствующие поля (например, Root .users) не будут добавлены в схему.
Модули напрямую сопоставляются с нижележащими пакетами PHP. Таким образом, при запуске Gato GraphQL как автономного приложения можно выборочно загружать только те модули/пакеты, которые нам нужны.
Например, если приложение выводит данные только для сообщений, категорий и тегов, то будут использоваться только пакеты posts-wp, categories-wp и tags-wp вместе с их зависимостями, которые должны быть загружены.
Затем при переходе с WordPress (скажем, на Laravel или Symfony) для новой платформы/CMS потребуется переопределить только эти 3 пакета, специфичные для WordPress.
Как следствие, вы можете использовать WordPress без головы уже сегодня, зная, что в будущем вы сможете перенести свое приложение на другую платформу или CMS с минимальными усилиями.
Переход на Gato GraphQL с другого API
Если вы уже используете безголовую WordPress, скорее всего, ваше приложение применяет WPGraphQL, либо WP REST API.
К сожалению, с любым из этих двух API вы привязаны к WordPress, то есть за пределами WordPress нет WP REST API, а WPGraphQL без WordPress работать не может.
К счастью, любой из этих API можно заменить на Gato GraphQL и получить возможность перенести свое безголовое приложение WordPress с WordPress [на что-то другое].
Потребуются эти 2 шага:
- Переход с WP REST API или WPGraphQL на Gato GraphQL
- Переопределение необходимых пакетов, специфичных для WordPress.
Посмотрим, как можно осуществить перенос API.
WP REST API для устойчивых запросов Gato GraphQL
При помощи расширения Persisted Queries вы можете публиковать REST-подобные конечные точки, составленные при помощи GraphQL.
Для каждой конечной точки REST в вашем приложении можно создать соответствующую конечную точку устойчивого запроса, которая получает те же данные, и использовать эту конечную точку.
Например, следующий запрос GraphQL может заменить конечную точку REST /wp-json/wp/v2/posts/:
{
posts {
id
date: dateStr(format: "Y-m-d\\TH:i:s")
modified: modifiedDateStr(format: "Y-m-d\\TH:i:s")
slug
status
link: url
title: self {
rendered: title
}
content: self {
rendered: content
},
excerpt: self {
rendered: excerpt
}
author
featured_media: featuredImage
sticky: isSticky
categories
tags
}
}
Благодаря иерархии API устойчивый запрос публикуется по пути /graphql-query/wp/v2/posts/. Это упрощает сопоставление конечных точек.
Чтобы реплицировать конечную точку REST /wp-json/wp/v2/posts/{id}/, которая извлекает данные для сообщения с заданным идентификатором, можно указать идентификатор сообщения в параметре URL-адреса postId.
Например, следующий устойчивый запрос может быть вызван конечной точкой /graphql-query/wp/v2/posts/single/?postId={id}:
query GetPost($postId: ID!) {
post(by: { id: $postId }) {
id
date: dateStr(format: "Y-m-d\\TH:i:s")
modified: modifiedDateStr(format: "Y-m-d\\TH:i:s")
slug
status
link: url
title: self {
rendered: title
}
content: self {
rendered: content
},
excerpt: self {
rendered: excerpt
}
author
featured_media: featuredImage
sticky: isSticky
categories
tags
}
}
От WPGraphQL к Gato GraphQL
Схемы GraphQL от WPGraphQL и Gato GraphQL схожи, но немного различаются, поэтому их нужно адаптировать.
Стартер WordPress Next.js leoloso/next-wordpress-starter работает с WPGraphQL или Gato GraphQL. Этот стартер для обоих серверов использует одну и ту же логику JS. Различаются только запросы GraphQL.
В стартере ниже представлено несколько примеров адаптации запросов между двумя серверами. Например, этот запрос WPGraphQL:
fragment PostFields on Post {
id
categories {
edges {
node {
databaseId
id
name
slug
}
}
}
databaseId
date
isSticky
postId
slug
title
}
…адаптирован для Gato GraphQL вот так:
fragment PostFields on Post {
id
categories: self {
edges: categories(pagination: { limit: -1 }) {
node: self {
databaseId: id
id
name
slug
}
}
}
databaseId: id
date: dateStr
isSticky
postId: id
slug
title
}
Подробности: запуск Gato GraphQL как безголового приложения PHP
Вот подробное объяснение демо-видео:
Мы предоставляем запрос GraphQL для запуска в файле retrive-github-artifacts.gql.
Запрос подключается к API GitHub, получая токен доступа из переменной среды GITHUB_ACCESS_TOKEN. Он динамически генерирует полный путь к конечной точке actions/artifacts из предоставленных переменных, а затем отправляет к ней HTTP-запрос.
После — из ответа — он извлекает «URL загрузки» из каждого элемента артефакта и отправляет к ним асинхронные HTTP-запросы. Из заголовка Location каждого из этих «URL загрузки» мы получаем фактический URL загружаемого файла.
Наконец, он выводит все URL вместе, разделенные пробелом, чтобы их было удобно скормить WP-CLI.
# Файл retrieve-github-artifacts.gql
query RetrieveProxyArtifactDownloadURLs(
$repoOwner: String!
$repoProject: String!
$perPage: Int = 1
$artifactName: String = ""
) {
githubAccessToken: _env(name: "GITHUB_ACCESS_TOKEN")
@remove
# Создаем заголовок авторизации для отправки на GitHub.
authorizationHeader: _sprintf(
string: "Bearer %s"
values: [$__githubAccessToken]
)
@remove
# Создаем заголовок авторизации для отправки на GitHub.
githubRequestHeaders: _echo(
value: [
{ name: "Accept", value: "application/vnd.github+json" }
{ name: "Authorization", value: $__authorizationHeader }
]
)
@remove
@export(as: "githubRequestHeaders")
githubAPIEndpoint: _sprintf(
string: "https://api.github.com/repos/%s/%s/actions/artifacts?per_page=%s&name=%s"
values: [$repoOwner, $repoProject, $perPage, $artifactName]
)
# Используем поле из "Send HTTP Request Fields", чтобы соединиться с GitHub
gitHubArtifactData: _sendJSONObjectItemHTTPRequest(
input: {
url: $__githubAPIEndpoint
options: { headers: $__githubRequestHeaders }
}
)
@remove
# Наконец, просто извлекаем URL из каждого элемента «артефактов».
gitHubProxyArtifactDownloadURLs: _objectProperty(
object: $__gitHubArtifactData
by: { key: "artifacts" }
)
@underEachArrayItem(passValueOnwardsAs: "artifactItem")
@applyField(
name: "_objectProperty"
arguments: { object: $artifactItem, by: { key: "archive_download_url" } }
setResultInResponse: true
)
@export(as: "gitHubProxyArtifactDownloadURLs")
}
query CreateHTTPRequestInputs
@depends(on: "RetrieveProxyArtifactDownloadURLs")
{
httpRequestInputs: _echo(value: $gitHubProxyArtifactDownloadURLs)
@underEachArrayItem(passValueOnwardsAs: "url")
@applyField(
name: "_objectAddEntry"
arguments: {
object: {
options: { headers: $githubRequestHeaders, allowRedirects: null }
}
key: "url"
value: $url
}
setResultInResponse: true
)
@export(as: "httpRequestInputs")
@remove
}
query RetrieveActualArtifactDownloadURLs
@depends(on: "CreateHTTPRequestInputs")
{
_sendHTTPRequests(inputs: $httpRequestInputs) {
artifactDownloadURL: header(name: "Location")
@export(as: "artifactDownloadURLs", type: LIST)
}
}
query PrintSpaceSeparatedArtifactDownloadURLs
@depends(on: "RetrieveActualArtifactDownloadURLs")
{
spaceSeparatedArtifactDownloadURLs: _arrayJoin(
array: $artifactDownloadURLs
separator: " "
)
}
Логика PHP напрямую загружает код из плагина Gato GraphQL и из пакета All Extensions (он необходим для отправки HTTP-запросов и другой функциональности).
Так как PHP-приложение автономно, мы должны явно указать, какие модули инициализируются, и предоставить любую конфигурацию, отличную от конфигурации по умолчанию.
Например, скажем модулю SendHTTPRequests разрешить подключение к https://api.github.com/repos, а модулю EnvironmentFields разрешить переменную среды для доступа GITHUB_ACCESS_TOKEN.
Обратите внимание, что схема GraphQL генерируется при первом выполнении запроса GraphQL и кэшируется на диске. Таким образом, начиная со второго раза, не выполняется никакой код вычисления схемы. Это ускоряет выполнение.
Наконец, автономное приложение инициализирует сервер GraphQL, выполняет к нему запрос и выводит ответ.
<?php
// Файл retrieve-github-artifacts.php
declare(strict_types=1);
use GraphQLByPoP\GraphQLServer\Server\StandaloneGraphQLServer;
use PoP\Root\Container\ContainerCacheConfiguration;
//Загрузка сервера GraphQL через автономные компоненты PHP.
require_once (__DIR__ . '/wordpress/wp-content/plugins/gatographql/vendor/scoper-autoload.php');
// Загрузка расширений PRO через автономные компоненты PHP.
require_once (__DIR__ . '/wordpress/wp-content/plugins/gatographql-all-extensions-bundle/vendor/scoper-autoload.php');
// Модули, необходимые в запросе GraphQL
$moduleClasses = [
\PoPSchema\EnvironmentFields\Module::class,
\PoPSchema\FunctionFields\Module::class,
\GraphQLByPoP\ExportDirective\Module::class,
\GraphQLByPoP\DependsOnOperationsDirective\Module::class,
\GraphQLByPoP\RemoveDirective\Module::class,
\PoPSchema\ApplyFieldDirective\Module::class,
\PoPSchema\SendHTTPRequests\Module::class,
\PoPSchema\ConditionalMetaDirectives\Module::class,
\PoPSchema\DataIterationMetaDirectives\Module::class,
];
// Конфигурация модулей
$moduleClassConfiguration = [
\PoP\GraphQLParser\Module::class => [
\PoP\GraphQLParser\Environment::ENABLE_MULTIPLE_QUERY_EXECUTION => true,
\PoP\GraphQLParser\Environment::USE_LAST_OPERATION_IN_DOCUMENT_FOR_MULTIPLE_QUERY_EXECUTION_WHEN_OPERATION_NAME_NOT_PROVIDED => true,
\PoP\GraphQLParser\Environment::ENABLE_RESOLVED_FIELD_VARIABLE_REFERENCES => true,
\PoP\GraphQLParser\Environment::ENABLE_COMPOSABLE_DIRECTIVES => true,
],
\PoPSchema\SendHTTPRequests\Module::class => [
\PoPSchema\SendHTTPRequests\Environment::SEND_HTTP_REQUEST_URL_ENTRIES => [
'#https://api.github.com/repos/(.*)#',
],
],
\PoPSchema\EnvironmentFields\Module::class => [
\PoPSchema\EnvironmentFields\Environment::ENVIRONMENT_VARIABLE_OR_PHP_CONSTANT_ENTRIES => [
'GITHUB_ACCESS_TOKEN',
],
],
];
// Кеширование схемы на диск, чтобы ускорить выполнение со второго раза.
$containerCacheConfiguration = new ContainerCacheConfiguration('MyGraphQLServer', true, 'retrieve-github-artifacts', __DIR__ . '/tmp');
// Инициализация сервера
$graphQLServer = new StandaloneGraphQLServer($moduleClasses, $moduleClassConfiguration, [], [], $containerCacheConfiguration);
/**
* Запрос GraphQL для выполнения, хранящийся в собственном файле .gql.
*
* @var string
*/
$query = file_get_contents(__DIR__ . '/retrieve-github-artifacts.gql');
// Переменные GraphQL
$variables = [
'repoOwner' => 'GatoGraphQL',
'repoProject' => 'GatoGraphQL',
'perPage' => 3
];
// Выполнение запроса
$response = $graphQLServer->execute(
$query,
$variables,
);
// Вывод запроса
echo $response->getContent();
Чтобы выполнить запрос GraphQL, запустим файл в терминале, используя jq для красивой печати вывода JSON:
php retrieve-github-artifacts.php | jq
Наконец, чтобы извлечь URL-адреса артефактов из ответа GraphQL и скормить их WP-CLI, запускаем:
GITHUB_ARTIFACT_URLS=$(php retrieve-github-artifacts.php \
| grep -E -o '"spaceSeparatedArtifactDownloadURLs\":"(.*)"' \
| cut -d':' -f2- | cut -d'"' -f2- | rev | cut -d'"' -f2- | rev \
| sed 's/\\\//\//g')
wp plugin install ${GITHUB_ARTIFACT_URLS} --force --activate
Как показано в видео, мы можем запустить Gato GraphQL без WordPress.
Читайте также:
- Почему стоит задуматься о разработке статических сайтов
- Как посещать сайты, не заходя в браузер
- 5 вещей, которые следует учесть перед тем, как создать свой бизнес на основе мобильного приложения
Читайте нас в Telegram, VK и Дзен
Перевод статьи Leonardo Losoviz: Introducing: Headless WordPress without WordPress





