Pobieranie danych o dynamicznej strukturze
W WordPress możemy pobierać zagnieżdżone poziomy danych, tj. encje zawierające elementy podrzędne tego samego typu. Na przykład menu zawiera elementy, które mogą mieć podelementy, a te podelementy mogą same zawierać podelementy — i tak dalej przez kilka poziomów. Podobnie komentarz może mieć odpowiedzi, które same w sobie również mogą mieć odpowiedzi.
Zobaczmy, jak pracować z menu w GraphQL. Pobieranie danych menu w GraphQL polega na odpytywaniu elementów wewnątrz menu na wszystkich różnych poziomach. Na przykład w poniższej query menu ma 3 poziomy, a my używamy fragmentu MenuItemProps, aby pobrać te same pola (id, label i url) dla wszystkich elementów menu na wszystkich poziomach:
query GetMenu {
menu(by: { id: 176 }) {
id
items {
...MenuItemProps
children {
...MenuItemProps
children {
...MenuItemProps
}
}
}
}
}
fragment MenuItemProps on MenuItem {
id
label
url
}Jak widać, liczba poziomów odzwierciedla się w query GraphQL. Ponieważ menu w aplikacji ma 3 poziomy, query GraphQL ma 3 poziomy zagnieżdżenia.
Jednak w WordPress tworzenie menu nie jest z góry określone — jest konfigurowane przez administratora witryny za pomocą ekranu Menu (tj. gdy nie używa się „motywu blokowego") i przechowywane w bazie danych:

Stwarza to problem: gdy dodajemy dodatkowy poziom do menu za pomocą interfejsu użytkownika, musimy również dodać dodatkowy poziom do query GraphQL, w przeciwnym razie nowy poziom nie będzie wyświetlany na stronie.
Istnieją 2 sposoby radzenia sobie z tym problemem. Prostszy polega na stworzeniu query GraphQL pobierającej więcej poziomów niż początkowo potrzeba, tak aby było miejsce na dalsze dodawanie poziomów. Na przykład jeśli aplikacja potrzebuje 3 poziomów, query GraphQL mogłaby mimo to pobierać dane dla 6 (lub 10 czy 20) poziomów, dając nam wystarczająco dużo miejsca na rozszerzanie menu aż do osiągnięcia limitu:
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
}Drugie rozwiązanie polega na użyciu pola Menu.itemDataEntries, które wyprodukuje ustrukturyzowany JSONObject z całością danych menu, w tym wszystkimi poziomami i podpoziomami:
query GetMenu {
menu(by: { id: 176 }) {
id
itemDataEntries
}
}Odpowiedź na tę query wygląda następująco:
{
"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": []
}
]
}
}
}Ta metoda ma tę zaletę, że pobierane dane są w całości determinowane przez interfejs użytkownika i odzwierciedlają dokładnie to, co jest przechowywane w bazie danych — dzięki czemu aplikacja nigdy nie musiałaby być aktualizowana przy dodawaniu dodatkowych poziomów do menu, czy to 2, czy 20.
Jednak ta metoda ma wyraźną wadę: tracimy silne typowanie z GraphQL. Zamiast otrzymywać element menu z silnie typowanymi polami, takimi jak url jako URL, label jako String, objectID jako ID i tak dalej, otrzymujemy zwykły obiekt, który nie będzie rozumiany przez narzędzia i klientów GraphQL, takie jak Apollo client czy Relay. W efekcie nie w pełni skorzystamy z zalet GraphQL.
Pobieranie danych ustawień WordPress
Inny problem pojawia się, gdy musimy pobierać encje determinowane przez interfejs użytkownika i przechowywane w bazie danych. Tak jest w przypadku ustawień w WordPress, gdzie nazwy opcji są dynamicznie tworzone przez motywy i wtyczki — a więc nie są z góry znane serwerowi GraphQL. Dotyczy to również wartości meta, które mogą być definiowane przez motywy i wtyczki i dlatego domyślnie nie są mapowane w schemacie GraphQL.
Z tego powodu schemat tworzony przez Gato GraphQL nie koduje na stałe nazw opcji ani ich typów, lecz są one dostępne za pomocą pola optionValue (a także optionValues i optionObjectValue), które przyjmuje nazwę opcji i zwraca wartość dowolnego możliwego wbudowanego typu (reprezentowanego przez AnyBuiltInScalar):
type Root {
optionValue(name: String!): AnyBuiltInScalar
}Ponieważ nie wszystkie opcje mają być udostępniane przez API, administrator witryny musi jawnie dodać je do listy dozwolonych — podając ich pełną nazwę lub wyrażenie regularne — w ustawieniach wtyczki:

Teraz query może pobierać opcje znajdujące się na liście dozwolonych:
{
siteURL: optionValue(name: "siteurl")
siteName: optionValue(name: "blogname")
siteDescription: optionValue(name: "blogdescription")
}Jeśli aplikacja potrzebuje dodatkowej opcji, można ją natychmiast udostępnić w API, dodając odpowiedni wpis do listy dozwolonych na stronie ustawień.