Obtener datos con estructura dinámica
En WordPress, podemos obtener niveles anidados de datos, es decir, entidades que contienen elementos hijos del mismo tipo. Por ejemplo, un menú contiene elementos que pueden tener subelementos, y esos subelementos a su vez pueden contener subelementos, y así sucesivamente durante varios niveles. De forma similar, un comentario puede tener respuestas que, ellas mismas, pueden tener respuestas.
Veamos cómo trabajar con menús en GraphQL. Obtener los datos del menú en GraphQL implica consultar los elementos dentro del menú para todos los diferentes niveles. Por ejemplo, en la consulta de abajo, el menú tiene 3 niveles, y usamos el fragment MenuItemProps para obtener los mismos campos (id, label y url) para todos los elementos del menú en todos los niveles:
query GetMenu {
menu(by: { id: 176 }) {
id
items {
...MenuItemProps
children {
...MenuItemProps
children {
...MenuItemProps
}
}
}
}
}
fragment MenuItemProps on MenuItem {
id
label
url
}Como puede apreciarse, el número de niveles se refleja en la consulta GraphQL. Como el menú en la aplicación tiene 3 niveles, la consulta GraphQL tiene 3 niveles de anidamiento.
Sin embargo, en WordPress la creación del menú no se decide de antemano, sino que la configura el administrador del sitio mediante la pantalla de Menús (esto es, cuando no se utiliza un "block theme"), y se almacena en la BD:

Esto presenta un problema: al añadir un nivel adicional al menú mediante la interfaz de usuario, también debemos añadir un nivel adicional a la consulta GraphQL, o de lo contrario el nuevo nivel no se mostrará en el sitio.
Hay 2 formas de tratar este problema. La más simple es crear la consulta GraphQL obteniendo más niveles de los necesarios inicialmente, de modo que haya espacio para seguir añadiendo niveles más adelante. Por ejemplo, si la aplicación necesita 3 niveles, la consulta GraphQL podría no obstante obtener datos para 6 (o 10 o 20) niveles, dándonos suficiente espacio para expandir el menú hasta alcanzar el límite:
query GetMenu {
menu(by: { id: 176 }) {
id
items {
...MenuItemProps
children {
...MenuItemProps
children {
...MenuItemProps
children {
...MenuItemProps
children {
...MenuItemProps
children {
...MenuItemProps
}
}
}
}
}
}
}
}
fragment MenuItemProps on MenuItem {
id
label
url
}La segunda solución es utilizar el campo Menu.itemDataEntries que producirá un JSONObject estructurado con la totalidad de los datos del menú, incluyendo todos los niveles y subniveles:
query GetMenu {
menu(by: { id: 176 }) {
id
itemDataEntries
}
}La respuesta a esta consulta se ve así:
{
"data": {
"menu": {
"id": 176,
"itemDataEntries": [
{
"id": 735,
"objectID": "6",
"parentID": null,
"label": "About The Tests",
"url": "https://mywpsite.com/about/",
"children": [
{
"id": 1451,
"objectID": "1133",
"parentID": "735",
"label": "Page Image Alignment",
"url": "https://mywpsite.com/about/page-image-alignment/",
"children": []
},
{
"id": 1452,
"objectID": "1134",
"parentID": "735",
"label": "Page Markup And Formatting",
"url": "https://mywpsite.com/about/page-markup-and-formatting/",
"children": []
}
]
},
{
"id": 739,
"objectID": "174",
"parentID": null,
"label": "Level 1",
"url": "https://mywpsite.com/level-1/",
"children": [
{
"id": 740,
"objectID": "173",
"parentID": "739",
"label": "Level 2",
"url": "https://mywpsite.com/level-1/level-2/",
"children": [
{
"id": 741,
"objectID": "172",
"parentID": "740",
"label": "Level 3",
"url": "https://mywpsite.com/level-1/level-2/level-3/",
"children": []
},
{
"id": 1453,
"objectID": "747",
"parentID": "740",
"label": "Level 3a",
"url": "https://mywpsite.com/level-1/level-2/level-3a/",
"children": []
},
{
"id": 1454,
"objectID": "748",
"parentID": "740",
"label": "Level 3b",
"url": "https://mywpsite.com/level-1/level-2/level-3b/",
"children": []
}
]
}
]
},
{
"id": 742,
"objectID": "146",
"parentID": null,
"label": "Lorem Ipsum",
"url": "https://mywpsite.com/lorem-ipsum/",
"children": []
}
]
}
}
}Este método tiene la ventaja de que los datos recuperados están completamente dirigidos por la interfaz de usuario, reflejando lo que está almacenado en la BD tal cual, de modo que la aplicación nunca necesitaría ser actualizada al añadir niveles adicionales al menú, sean 2 o 20 de ellos.
Sin embargo, este método tiene la clara desventaja de que perdemos el strong typing de GraphQL: en lugar de recibir un elemento de menú con campos fuertemente tipados url como una URL, label como un String, objectID como un ID, y así sucesivamente, obtenemos un objeto plano que no será comprendido por las herramientas y clientes de GraphQL, como el cliente Apollo o Relay. Por tanto, no aprovecharemos realmente los beneficios de GraphQL.
Obtener datos de ajustes de WordPress
Otro problema es cuando necesitamos obtener entidades que están dirigidas por la interfaz de usuario y almacenadas en la BD. Ese es el caso con los ajustes en WordPress, donde los nombres de las opciones son creados dinámicamente por themes y plugins, por lo que no son conocidos de antemano por el servidor GraphQL, y los valores meta, que también pueden ser definidos por themes y plugins, por lo que no se mapean por defecto al esquema GraphQL.
Por esta razón, el esquema producido por Gato GraphQL no codifica los nombres de las opciones ni sus tipos, sino que se accede a estos mediante un campo optionValue (y también optionValues y optionObjectValue) que recibe el nombre de la opción, y devuelve un valor de cualquier tipo built-in posible (representado por AnyBuiltInScalar):
type Root {
optionValue(name: String!): AnyBuiltInScalar
}Como no todas las opciones están pensadas para ser expuestas mediante la API, el administrador del sitio debe añadirlas explícitamente a la lista de permitidos, ya sea por su nombre completo o por una regex, en los ajustes del plugin:

Ahora, la consulta puede obtener las opciones de la whitelist:
{
siteURL: optionValue(name: "siteurl")
siteName: optionValue(name: "blogname")
siteDescription: optionValue(name: "blogdescription")
}Si hay una opción adicional que la aplicación necesita, puede hacerse inmediatamente disponible para la API simplemente añadiendo una entrada correspondiente a la lista de permitidos en la página de Ajustes.