Blog

🦸🏻‍♂️ Tanıtım: WordPress Olmadan Headless WordPress

Leonardo Losoviz
Yazan: Leonardo Losoviz ·

Matt Mullenweg ile WPEngine arasındaki rezalet başladığından bu yana, Reddit'te (ve başka yerlerde) giderek daha fazla insanın WordPress'e alternatif aradığını fark ettim; bu, WordPress'i hemen bırakmak için değil, hangi seçeneklerinin olduğunu ve olası bir geçişin ne kadar zahmetli olacağını anlamak için. Risklerini nasıl dağıtabileceklerini bilmek istiyorlar.

Headless WordPress ile çalışan geliştiriciler için Gato GraphQL artık harika yeni bir özellik sunuyor: WordPress Olmadan Headless WordPress.

Bu yazı, bunun nasıl mümkün olduğunu açıklayarak ve bir demo video göstererek konuyu tüm ayrıntılarıyla ele alıyor.

Gato GraphQL'i Bağımsız Bir PHP Uygulaması Olarak Çalıştırmak

Gato GraphQL, Composer aracılığıyla yönetilen bağımsız PHP bileşenleri kullanılarak oluşturulmuştur; bu sayede GraphQL sunucusunu oluşturan tüm PHP bileşenleri WordPress'e bağımlı değildir!

Bu nedenle, GraphQL sunucusu bağımsız bir PHP uygulaması olarak çalışabilir ve WordPress tabanlı ya da başka herhangi bir PHP uygulamasına dahil edilebilir.

Belirli bir kullanım senaryosunda uygulamanızın WordPress verilerine erişmesi gerekmiyorsa, en azından o senaryo için hazırsınız demektir.

Bu video, böyle bir kullanım senaryosunu göstermektedir: Geliştirme sırasında GitHub Actions'tan artifact indirmek/yüklemek amacıyla GitHub API'siyle etkileşim kurmak:

WordPress Olmadan Headless WordPress demosu: GraphQL query çalıştırma

Videoda, GraphQL query bir HTTP isteği göndererek GitHub Actions'ta üretilen en son Gato GraphQL eklentilerini alır; bu eklentiler, bir pull request merge edildiğinde artifact olarak yüklenir.

GraphQL yanıtındaki artifact URL'leri daha sonra WP-CLI'a enjekte edilerek eklentilerin yerel bir DEV web sunucusuna otomatik olarak yüklenmesi ve testlerin çalıştırılması sağlanır.

(Bu yazının son bölümünde daha ayrıntılı açıklayacağım.)

Bu kullanım senaryosunda hiçbir WordPress verisine erişilmediğinden, GraphQL sunucusu zaten bağımsız bir PHP uygulaması olarak çalışabilir.

İhtiyaç duysaydım, bunu GitHub Actions iş akışımın içinde bile kullanabilirdim!

Headless WordPress Uygulamasını Taşımak

WordPress verilerine eriştiğinizde, bunu WordPress olmadan nasıl çalıştıracağınıza bakalım.

Gato GraphQL tarafından sağlanan GraphQL şeması, WordPress verilerini getirmek için alanlar içerir: posts, users, comments, tags, categories vb.

WordPress verilerini getiren PHP resolver'larındaki kod WordPress'e bağımlıdır; bu kod WordPress olmayan bir uygulamada çalışamaz.

Ancak Gato GraphQL, bu resolver'ların her birini 2 paket aracılığıyla uygular:

  1. Tüm genel kodu içeren "vanilla" PHP paketi
  2. Bu resolver'ı karşılayan WordPress metodlarına yapılan gerçek çağrıları içeren WordPress'e özgü paket

Örneğin, şu GraphQL query'sinde:

{
  posts {
    id
    title
  }
}

...post getirme mantığı şunlardan oluşur:

  1. Root.posts alanı: Genel posts paketinde yer alır
  2. WordPress için get_posts metodu aracılığıyla çözümlenmesi: WordPress'e özgü posts-wp paketinde yer alır.

WordPress olmayan/WordPress paketleri arasındaki kod bölünmesi yaklaşık %80/20 oranındadır; yani kodun %80'i başka bir framework/CMS ile yeniden kullanılabilir, ve kodun yalnızca %20'sinin yeniden uygulanması gerekir.

Dahası, Gato GraphQL'deki tüm işlevler modüller aracılığıyla sunulur ve modüller istenildiğinde etkinleştirilebilir/devre dışı bırakılabilir.

Şema modülleri
Şema modülleri

Modules, güvenlik amacıyla uygulanan bir özelliktir: Genel API'nizde kullanıcı verilerini açığa çıkarmanıza gerek yoksa Users modülünü devre dışı bırakabilirsiniz; böylece ilgili alanlar (örneğin Root.users) şemaya hiçbir zaman eklenmez.

Modüller doğrudan temel PHP paketleriyle eşlenir. Bu nedenle, Gato GraphQL'i bağımsız bir uygulama olarak çalıştırırken ihtiyaç duyduğumuz modülleri/paketleri seçici olarak yükleyebilir, diğerlerini yüklemeyebiliriz.

Örneğin, uygulamanız yalnızca posts, categories ve tags verilerini gösteriyorsa, yalnızca posts-wp, categories-wp ve tags-wp paketlerinin (bağımlılıklarıyla birlikte) yüklenmesi gerekir.

Ardından, WordPress'ten başka bir platforma geçiş yaparken (diyelim ki Laravel veya Symfony'ye), yalnızca bu 3 WordPress'e özgü paketin yeni framework/CMS için yeniden uygulanması gerekir, başka hiçbir şey değil.

Sonuç olarak, bugün headless WordPress kullanabilir ve ileride uygulamanızı başka bir framework veya CMS'e minimum çabayla taşıyabileceğinizi bilerek güvenle ilerleyebilirsiniz.

Başka Bir API'den Gato GraphQL'e Geçiş

Halihazırda headless WordPress yapıyorsanız, büyük ihtimalle uygulamanız WP REST API veya WPGraphQL kullanıyordur.

Ne yazık ki, bu iki API'nin herhangi biriyle WordPress'e kilitlenirsiniz: WordPress dışında WP REST API yoktur ve WPGraphQL, WordPress olmadan çalışamaz.

Neyse ki, bunların herhangi birini Gato GraphQL ile değiştirmek ve headless WordPress uygulamanızı WordPress'ten taşıyabilme imkânı kazanmak mümkündür.

Bu durumda şu 2 adım gerekecektir:

  1. WP REST API veya WPGraphQL'den Gato GraphQL'e geçiş
  2. Gerekli WordPress'e özgü paketlerin yeniden uygulanması

API geçişinin nasıl yapılabileceğine bakalım.

WP REST API'dan Gato GraphQL'in Persisted Queries'ine

Persisted Queries eklentisi sayesinde GraphQL kullanarak oluşturulmuş REST benzeri endpoint'ler yayımlayabilirsiniz.

Uygulamanızdaki her REST endpoint için, aynı verileri döndüren karşılık gelen bir persisted query endpoint'i oluşturabilir ve bunun yerine o endpoint'i kullanabilirsiniz.

Örneğin, aşağıdaki GraphQL query'si /wp-json/wp/v2/posts/ REST endpoint'inin yerini alabilir:

{
  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 hiyerarşisi sayesinde, persisted query /graphql-query/wp/v2/posts/ yolu altında yayımlanabilir; bu da endpoint eşlemesini kolaylaştırır.

Belirli ID'ye sahip postun verilerini döndüren /wp-json/wp/v2/posts/{id}/ REST endpoint'ini çoğaltmak için, postId URL parametresi aracılığıyla post ID'sini sağlayabiliriz.

Örneğin, aşağıdaki persisted query /graphql-query/wp/v2/posts/single/?postId={id} endpoint'i altında çağrılabilir:

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'den Gato GraphQL'e

WPGraphQL ve Gato GraphQL'in GraphQL şemaları benzer olmakla birlikte biraz farklıdır, bu yüzden uyarlanmaları gerekir.

Next.js WordPress starter'ı leoloso/next-wordpress-starter, hem WPGraphQL hem de Gato GraphQL ile çalışır. Starter, her iki sunucu için de aynı JS mantığını kullanır; yalnızca GraphQL queries farklıdır.

Bu starter, iki sunucu arasındaki queries uyarlamasına ilişkin çeşitli örnekler sunar. Örneğin, bu WPGraphQL query'si:

fragment PostFields on Post {
  id
  categories {
    edges {
      node {
        databaseId
        id
        name
        slug
      }
    }
  }
  databaseId
  date
  isSticky
  postId
  slug
  title
}

...Gato GraphQL için şu şekilde uyarlanmıştır:

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
}

Ayrıntılı Olarak: Gato GraphQL'i Bağımsız Bir PHP Uygulaması Olarak Çalıştırmak

İşte daha önce gösterilen demo videonun ayrıntılı açıklaması.

Çalıştırılacak GraphQL query'sini retrieve-github-artifacts.gql dosyası altında sağlıyoruz.

Query, GITHUB_ACCESS_TOKEN ortam değişkeninden erişim token'ını alarak GitHub API'sine bağlanır. Sağlanan değişkenlerden actions/artifacts endpoint'inin tam yolunu dinamik olarak oluşturur ve ardından buna bir HTTP isteği gönderir.

Yanıttan, her artifact öğesi içindeki "indirme URL'sini" çıkarır ve bunlara asenkron HTTP istekleri gönderir. Bu "indirme URL'lerinin" her birinin Location başlığından, indirilebilir dosyanın gerçek URL'sini elde ederiz.

Son olarak, WP-CLI'a enjekte etmeyi kolaylaştırmak için tüm URL'leri bir boşlukla ayrılmış şekilde yazdırır.

# 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: " "
  )
}

PHP mantığı, Gato GraphQL eklentisindeki ve "Power Extensions" paketindeki (HTTP istekleri göndermek ve diğer işlevler için gerekli) kodu doğrudan yükler.

Bağımsız bir PHP uygulaması olarak, hangi modüllerin başlatıldığını açıkça belirtmemiz ve varsayılan dışı yapılandırmalar sağlamamız gerekir.

Örneğin, SendHTTPRequests modülüne https://api.github.com/repos adresine bağlanmasına izin veriyoruz ve EnvironmentFields modülüne GITHUB_ACCESS_TOKEN ortam değişkenine erişmesine izin veriyoruz.

GraphQL şemasının, GraphQL query'si ilk kez çalıştırıldığında oluşturulduğunu ve diske önbelleğe alındığını unutmayın. Bu sayede, 2. seferden itibaren şemayı hesaplamak için hiçbir kod çalıştırılmaz ve bu da çalıştırmayı daha hızlı kılar.

Son olarak, bağımsız uygulama GraphQL sunucusunu başlatır, query'yi çalıştırır ve yanıtı yazdırır.

<?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();

GraphQL query'sini çalıştırmak için terminalde şunu çalıştırıyoruz (jq kullanarak JSON çıktısını güzel bir şekilde yazdırıyoruz):

php retrieve-github-artifacts.php | jq

Son olarak, artifact URL'lerini GraphQL yanıtından çıkarmak ve WP-CLI'a enjekte etmek için şunu çalıştırıyoruz:

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

Videoda gösterildiği gibi, Gato GraphQL'i WordPress olmadan çalıştırabiliyoruz.


Bültenimize abone olun

Gato GraphQL'deki tüm güncellemelerden haberdar olun.