๐ฆธ๐ปโโ๏ธ Memperkenalkan: Headless WordPress tanpa WordPress
Sejak polemik Matt Mullenweg vs WPEngine, saya semakin banyak melihat orang di Reddit (dan di tempat lain) mencari alternatif untuk WordPress, tidak harus untuk meninggalkan WordPress (setidaknya tidak segera), tetapi untuk memahami pilihan apa yang mereka miliki, dan seberapa menyakitkan potensi migrasi tersebut. Mereka ingin tahu cara mengantisipasi berbagai kemungkinan.
Bagi mereka yang bekerja dengan headless WordPress, Gato GraphQL kini menawarkan fitur baru yang keren: Headless WordPress tanpa WordPress.
Posting ini menjelaskan semuanya, menguraikan bagaimana hal ini mungkin dilakukan, dan menampilkan video demonstrasinya.
Menjalankan Gato GraphQL sebagai aplikasi PHP mandiri
Gato GraphQL dibangun menggunakan komponen PHP mandiri, dikelola melalui Composer, sedemikian rupa sehingga semua komponen PHP yang membentuk server GraphQL tidak bergantung pada WordPress!
Oleh karena itu, server GraphQL dapat berjalan sebagai aplikasi PHP mandiri, dan Anda dapat menyertakannya dalam aplikasi PHP apa pun, berbasis WordPress atau yang lainnya.
Jika untuk suatu kasus penggunaan aplikasi Anda tidak perlu mengakses data WordPress, maka, setidaknya untuk kasus penggunaan tersebut, Anda siap untuk memulai.
Video ini mendemonstrasikan kasus penggunaan seperti itu: Berinteraksi dengan API GitHub, untuk mengunduh/menginstal artefak dari GitHub Actions saat pengembangan:
Dalam video tersebut, query GraphQL menjalankan permintaan HTTP untuk mengambil plugin Gato GraphQL terbaru yang dihasilkan di GitHub Actions, yang diunggah sebagai artefak saat menggabungkan pull request.
URL artefak dari respons GraphQL kemudian diinjeksikan ke WP-CLI, agar plugin terinstal secara otomatis di server web lokal DEV, untuk menjalankan pengujian.
(Saya akan menjelaskan lebih rinci di bagian terakhir posting ini.)
Dalam kasus penggunaan ini, karena tidak ada data WordPress yang diakses sama sekali, server GraphQL sudah dapat berjalan sebagai aplikasi PHP mandiri.
Jika diperlukan, saya bahkan bisa menggunakannya di dalam workflow GitHub Actions saya!
Migrasi aplikasi headless WordPress
Setiap kali Anda mengakses data WordPress, mari kita lihat cara menjalankannya tanpa WordPress.
Skema GraphQL yang disediakan oleh Gato GraphQL berisi field untuk mengambil data WordPress: posts, users, comments, tags, categories, dll.
Kode dalam resolver PHP yang mengambil data WordPress bergantung pada WordPress; kode tersebut tidak dapat berjalan di aplikasi non-WordPress.
Namun, Gato GraphQL memiliki setiap resolver yang diimplementasikan melalui 2 paket:
- Satu paket PHP "vanilla", berisi semua kode generik
- Satu paket spesifik WordPress, berisi pemanggilan aktual ke metode WordPress yang memenuhi resolver tersebut
Misalnya, dalam query GraphQL ini:
{
posts {
id
title
}
}...logika untuk mengambil posts terdiri dari:
- Field
Root.posts: Berada di paketpostsyang generik - Resolusinya untuk WordPress melalui metode
get_posts: Berada di paketposts-wpyang spesifik WordPress.
Pembagian kode antara paket non-WordPress/WordPress adalah sekitar 80/20%, artinya 80% kode dapat digunakan kembali dengan framework/CMS lain, dan hanya 20% kode yang perlu diimplementasikan ulang.
Selain itu, semua fungsionalitas di Gato GraphQL dikirimkan melalui modul, dan modul dapat diaktifkan/dinonaktifkan sesuai keinginan.

Modules adalah fitur yang diimplementasikan untuk tujuan keamanan: Jika Anda tidak perlu mengekspos data pengguna di API publik Anda, maka Anda dapat menonaktifkan modul Users, dan field terkait (seperti Root.users) tidak akan pernah ditambahkan ke skema.
Modul dipetakan langsung ke paket PHP yang mendasarinya. Oleh karena itu, saat menjalankan Gato GraphQL sebagai aplikasi mandiri, kita dapat secara selektif memuat modul/paket yang kita butuhkan, dan tidak ada yang lainnya.
Misalnya, jika aplikasi Anda hanya mencetak data untuk posts, categories, dan tags, maka hanya paket posts-wp, categories-wp, dan tags-wp (beserta dependensinya) yang perlu dimuat.
Kemudian, saat bermigrasi dari WordPress (misalnya, ke Laravel, atau Symfony), hanya 3 paket spesifik WordPress tersebut yang perlu diimplementasikan ulang untuk framework/CMS baru, dan tidak ada yang lainnya.
Akibatnya, Anda dapat menggunakan headless WordPress hari ini, mengetahui bahwa ke depannya Anda dapat migrasi aplikasi Anda ke framework atau CMS lain dengan upaya minimal.
Beralih ke Gato GraphQL dari API lain
Jika Anda sudah melakukan headless WordPress, kemungkinan aplikasi Anda menggunakan WP REST API atau WPGraphQL.
Sayangnya, dengan salah satu dari dua API ini Anda terikat ke WordPress: Tidak ada WP REST API di luar WordPress, dan WPGraphQL tidak dapat berjalan tanpa WordPress.
Untungnya, dimungkinkan untuk mengganti salah satunya dengan Gato GraphQL, dan mendapatkan kemampuan untuk migrasi aplikasi headless WordPress Anda dari WordPress.
Dua langkah ini kemudian diperlukan:
- Transisi dari WP REST API atau WPGraphQL ke Gato GraphQL
- Implementasikan ulang paket spesifik WordPress yang diperlukan
Mari kita lihat bagaimana transisi API dapat dilakukan.
WP REST API ke persisted queries Gato GraphQL
Dengan ekstensi Persisted Queries Anda dapat mempublikasikan endpoint mirip REST, yang disusun menggunakan GraphQL.
Untuk setiap endpoint REST di aplikasi Anda, Anda dapat membuat endpoint persisted query yang sesuai untuk mengambil data yang sama, dan menggunakan endpoint tersebut sebagai gantinya.
Misalnya, query GraphQL berikut dapat menggantikan endpoint 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
}
}Berkat hirarki API, persisted query dapat dipublikasikan di bawah path /graphql-query/wp/v2/posts/, sehingga mudah untuk memetakan endpoint.
Untuk mereplikasi endpoint REST /wp-json/wp/v2/posts/{id}/, yang mengambil data untuk post dengan ID tertentu, kita dapat menyediakan ID post di bawah parameter URL postId.
Misalnya, persisted query berikut dapat dipanggil di bawah endpoint /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 ke Gato GraphQL
Skema GraphQL dari WPGraphQL dan Gato GraphQL serupa tetapi sedikit berbeda, sehingga perlu diadaptasi.
Starter Next.js WordPress leoloso/next-wordpress-starter berjalan dengan WPGraphQL atau Gato GraphQL. Starter ini menggunakan logika JS yang sama untuk kedua server, hanya query GraphQL-nya yang berbeda.
Starter ini menyediakan beberapa contoh adaptasi query antara kedua server. Misalnya, query WPGraphQL ini:
fragment PostFields on Post {
id
categories {
edges {
node {
databaseId
id
name
slug
}
}
}
databaseId
date
isSticky
postId
slug
title
}...diadaptasi seperti ini untuk 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
}Secara rinci: Menjalankan Gato GraphQL sebagai aplikasi PHP mandiri
Berikut adalah penjelasan rinci dari video demo sebelumnya.
Kita menyediakan query GraphQL yang akan dijalankan dalam file retrieve-github-artifacts.gql.
Query tersebut terhubung ke API GitHub dengan mendapatkan token akses dari variabel lingkungan GITHUB_ACCESS_TOKEN. Query ini secara dinamis menghasilkan path lengkap untuk endpoint actions/artifacts dari variabel yang disediakan, kemudian mengirimkan permintaan HTTP ke sana.
Dari respons, query mengekstrak "download URL" dari setiap item artefak, dan mengirimkan permintaan HTTP asinkron ke sana. Dari header Location setiap "download URL" tersebut, kita mendapatkan URL aktual dari file yang dapat diunduh.
Terakhir, query mencetak semua URL bersama-sama dipisahkan oleh spasi, agar mudah diinjeksikan ke WP-CLI.
# File retrieve-github-artifacts.gql
query RetrieveProxyArtifactDownloadURLs(
$repoOwner: String!
$repoProject: String!
$perPage: Int = 1
$artifactName: String = ""
) {
githubAccessToken: _env(name: "GITHUB_ACCESS_TOKEN")
@remove
# Create the authorization header to send to GitHub
authorizationHeader: _sprintf(
string: "Bearer %s"
values: [$__githubAccessToken]
)
@remove
# Create the authorization header to send to 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]
)
# Use the field from "Send HTTP Request Fields" to connect to GitHub
gitHubArtifactData: _sendJSONObjectItemHTTPRequest(
input: {
url: $__githubAPIEndpoint
options: { headers: $__githubRequestHeaders }
}
)
@remove
# Finally just extract the URL from within each "artifacts" item
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: " "
)
}Logika PHP langsung memuat kode dari plugin Gato GraphQL, dan dari bundle "Power Extensions" (diperlukan untuk mengirim permintaan HTTP, dan fungsionalitas lainnya).
Sebagai aplikasi PHP mandiri, kita harus secara eksplisit menunjukkan modul apa yang diinisialisasi, dan menyediakan konfigurasi non-default apa pun.
Misalnya, kita memberi tahu modul SendHTTPRequests untuk mengizinkan koneksi ke https://api.github.com/repos, dan modul EnvironmentFields untuk mengizinkan akses ke variabel lingkungan GITHUB_ACCESS_TOKEN.
Perhatikan bahwa skema GraphQL dihasilkan pertama kali query GraphQL dijalankan, dan di-cache ke disk. Dengan cara ini, dari percobaan ke-2 dan seterusnya, tidak ada kode untuk menghitung skema yang dijalankan, sehingga eksekusi lebih cepat.
Terakhir, aplikasi mandiri menginisialisasi server GraphQL, menjalankan query terhadapnya, dan mencetak respons.
<?php
// File retrieve-github-artifacts.php
declare(strict_types=1);
use GraphQLByPoP\GraphQLServer\Server\StandaloneGraphQLServer;
use PoP\Root\Container\ContainerCacheConfiguration;
// Load the GraphQL server via the standalone PHP components
require_once (__DIR__ . '/wordpress/wp-content/plugins/gatographql/vendor/scoper-autoload.php');
// Load the PRO extensions via the standalone PHP components
require_once (__DIR__ . '/wordpress/wp-content/plugins/gatographql-power-extensions-bundle/vendor/scoper-autoload.php');
// Modules required in the GraphQL query
$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,
];
// Configure the modules
$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',
],
],
];
// Cache the schema to disk, to speed-up execution from the 2nd time onwards
$containerCacheConfiguration = new ContainerCacheConfiguration('MyGraphQLServer', true, 'retrieve-github-artifacts', __DIR__ . '/tmp');
// Initialize the server
$graphQLServer = new StandaloneGraphQLServer($moduleClasses, $moduleClassConfiguration, [], [], $containerCacheConfiguration);
/**
* GraphQL query to execute, stored in its own .gql file
*
* @var string
*/
$query = file_get_contents(__DIR__ . '/retrieve-github-artifacts.gql');
// GraphQL variables
$variables = [
'repoOwner' => 'GatoGraphQL',
'repoProject' => 'GatoGraphQL',
'perPage' => 3
];
// Execute the query
$response = $graphQLServer->execute(
$query,
$variables,
);
// Print the response
echo $response->getContent();Untuk menjalankan query GraphQL, kita jalankan di terminal (menggunakan jq untuk mencetak output JSON dengan format yang rapi):
php retrieve-github-artifacts.php | jqTerakhir, untuk mengekstrak URL artefak dari respons GraphQL, dan menginjeksikannya ke WP-CLI, kita jalankan:
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 --activateSeperti yang ditunjukkan dalam video, kita dapat menjalankan Gato GraphQL tanpa WordPress.