Interactuar con la API GraphQL
Interactuar con la API GraphQLEjecutar Gato GraphQL sin WordPress

Ejecutar Gato GraphQL sin WordPress

Gato GraphQL ha sido construido utilizando componentes PHP independientes, gestionados mediante Composer, de tal manera que ¡todos los componentes PHP que conforman el servidor GraphQL no dependen de WordPress!

Como tal, el servidor GraphQL puede ejecutarse como una aplicación PHP independiente, y puedes incluirlo dentro de cualquier aplicación PHP, basada en WordPress o cualquier otra cosa.

Si para algún caso de uso tu aplicación no necesita acceder a datos de WordPress, entonces, al menos para ese caso de uso, estás listo para empezar.

Este vídeo demuestra un caso de uso así: interactuando con la API de GitHub, para descargar/instalar artefactos desde GitHub Actions durante el desarrollo:

Demo de Headless WordPress sin Wordpress: ejecutando consulta GraphQL

En el vídeo, la consulta GraphQL ejecuta una petición HTTP para obtener los últimos plugins de Gato GraphQL generados en GitHub Actions, que se suben como artefactos al hacer merge de un pull request.

Las URLs de los artefactos de la respuesta GraphQL se inyectan luego en WP-CLI, para que los plugins se instalen automáticamente en un servidor web DEV local, para ejecutar pruebas.

En este caso de uso, como no se accede a ningún dato de WordPress en absoluto, el servidor GraphQL ya puede ejecutarse como una app PHP independiente.

En detalle: Ejecutar Gato GraphQL como una app PHP independiente

Aquí está la explicación en detalle del vídeo demo.

Proporcionamos la consulta GraphQL a ejecutar en el archivo retrieve-github-artifacts.gql.

La consulta se conecta a la API de GitHub obteniendo el token de acceso desde la variable de entorno GITHUB_ACCESS_TOKEN. Genera dinámicamente la ruta completa para el endpoint actions/artifacts a partir de las variables proporcionadas, y luego envía una petición HTTP contra él.

A partir de la respuesta, extrae luego la "download URL" desde cada elemento de artefacto, y envía peticiones HTTP asíncronas contra ellas. Desde la cabecera Location de cada una de estas "download URLs", obtenemos la URL real del archivo descargable.

Finalmente, imprime todas las URLs juntas separadas por un espacio, para que sea conveniente inyectarlas en WP-CLI.

# Archivo retrieve-github-artifacts.gql
 
query RetrieveProxyArtifactDownloadURLs(
  $repoOwner: String!
  $repoProject: String!
  $perPage: Int = 1
  $artifactName: String = ""
) {
  githubAccessToken: _env(name: "GITHUB_ACCESS_TOKEN")
    @remove
 
  # Crear la cabecera de autorización para enviar a GitHub
  authorizationHeader: _sprintf(
    string: "Bearer %s"
    values: [$__githubAccessToken]
  )
    @remove
 
  # Crear la cabecera de autorización para enviar a 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]
  )
 
  # Usar el campo de "Send HTTP Request Fields" para conectarse a GitHub
  gitHubArtifactData: _sendJSONObjectItemHTTPRequest(
    input: {
      url: $__githubAPIEndpoint
      options: { headers: $__githubRequestHeaders }
    }
  )
    @remove
 
  # Finalmente solo extraer la URL desde cada elemento "artifacts"
  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: " "
  )
}

La lógica PHP carga directamente el código del plugin Gato GraphQL, y del bundle "Power Extensions" (necesario para enviar peticiones HTTP, y otra funcionalidad).

Como una app PHP independiente, debemos indicar explícitamente qué módulos se inicializan, y proporcionar cualquier configuración no por defecto.

Por ejemplo, le decimos al módulo SendHTTPRequests que permita conectarse a https://api.github.com/repos, y al módulo EnvironmentFields que permita acceder a la variable de entorno GITHUB_ACCESS_TOKEN.

Ten en cuenta que el esquema GraphQL se genera la primera vez que se ejecuta la consulta GraphQL, y se cachea en disco. De este modo, a partir de la 2ª vez, no se ejecuta ninguno del código para calcular el esquema, haciendo la ejecución más rápida.

Finalmente, la app independiente inicializa el servidor GraphQL, ejecuta la consulta contra él, e imprime la respuesta.

<?php
// Archivo retrieve-github-artifacts.php
 
declare(strict_types=1);
 
use GraphQLByPoP\GraphQLServer\Server\StandaloneGraphQLServer;
use PoP\Root\Container\ContainerCacheConfiguration;
 
// Cargar el servidor GraphQL mediante los componentes PHP independientes
require_once (__DIR__ . '/wordpress/wp-content/plugins/gatographql/vendor/scoper-autoload.php');
 
// Cargar las extensiones PRO mediante los componentes PHP independientes
require_once (__DIR__ . '/wordpress/wp-content/plugins/gatographql-power-extensions-bundle/vendor/scoper-autoload.php');
 
// Módulos requeridos en la consulta 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,
];
 
// Configurar los módulos
$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',
    ],
  ],
];
 
// Cachear el esquema en disco, para acelerar la ejecución a partir de la 2ª vez
$containerCacheConfiguration = new ContainerCacheConfiguration('MyGraphQLServer', true, 'retrieve-github-artifacts', __DIR__ . '/tmp');
 
// Inicializar el servidor
$graphQLServer = new StandaloneGraphQLServer($moduleClasses, $moduleClassConfiguration, [], [], $containerCacheConfiguration);
 
/**
 * Consulta GraphQL a ejecutar, almacenada en su propio archivo .gql
 *
 * @var string
 */
$query = file_get_contents(__DIR__ . '/retrieve-github-artifacts.gql');
 
// Variables GraphQL
$variables = [
  'repoOwner' => 'GatoGraphQL',
  'repoProject' => 'GatoGraphQL',
  'perPage' => 3
];
 
// Ejecutar la consulta
$response = $graphQLServer->execute(
  $query,
  $variables,
);
 
// Imprimir la respuesta
echo $response->getContent();

Para ejecutar la consulta GraphQL, ejecutamos en el terminal (usando jq para imprimir el JSON de forma legible):

php retrieve-github-artifacts.php | jq

Finalmente, para extraer las URLs de los artefactos de la respuesta GraphQL, e inyectarlas en WP-CLI, ejecutamos:

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