Koncepcje, Idee, Strategie
Koncepcje, Idee, StrategiePobieranie danych o dynamicznej strukturze

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:

Tworzenie menu w WordPress

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:

Dodawanie opcji do listy dozwolonych na stronie ustawień
Dodawanie opcji do listy dozwolonych na stronie ustawień

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ń.