Концепции, идеи, стратегии
Концепции, идеи, стратегииПолучение данных с динамической структурой

Получение данных с динамической структурой

В WordPress можно получать вложенные уровни данных, то есть сущности, содержащие дочерние элементы того же типа. Например, меню содержит элементы, у которых могут быть подэлементы, а те, в свою очередь, тоже могут содержать подэлементы, и так далее на нескольких уровнях. Аналогичным образом, комментарий может иметь ответы, которые сами могут содержать ответы.

Посмотрим, как работать с меню в GraphQL. Получение данных меню в GraphQL предполагает запрос элементов внутри меню для всех различных уровней. Например, в приведённом ниже запросе меню имеет 3 уровня, и мы используем фрагмент MenuItemProps для получения одних и тех же полей (id, label и url) для всех элементов меню на всех уровнях:

query GetMenu {
  menu(by: { id: 176 }) {
    id
    items {
      ...MenuItemProps
      children {
        ...MenuItemProps
        children {
          ...MenuItemProps
        }
      }
    }
  }
}
 
fragment MenuItemProps on MenuItem {
  id
  label
  url
}

Как можно заметить, количество уровней отражается в GraphQL-запросе. Поскольку меню в приложении имеет 3 уровня, GraphQL-запрос тоже содержит 3 уровня вложенности.

Однако в WordPress структура меню не определяется заранее — она настраивается администратором сайта через экран Меню (то есть когда не используется «блочная тема») и хранится в БД:

Создание меню в WordPress

Это создаёт проблему: при добавлении дополнительного уровня в меню через пользовательский интерфейс необходимо также добавить дополнительный уровень в GraphQL-запрос, иначе новый уровень не будет отображаться на сайте.

Существует 2 способа решить эту проблему. Более простой — создать GraphQL-запрос, извлекающий больше уровней, чем требуется изначально, чтобы в дальнейшем оставалось место для добавления новых уровней. Например, если приложению нужны 3 уровня, GraphQL-запрос может тем не менее извлекать данные для 6 (или 10, или 20) уровней, что даёт достаточно места для расширения меню вплоть до достижения лимита:

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
}

Второе решение — использовать поле Menu.itemDataEntries, которое сформирует структурированный JSONObject со всеми данными меню, включая все уровни и подуровни:

query GetMenu {
  menu(by: { id: 176 }) {
    id
    itemDataEntries
  }
}

Ответ на этот запрос выглядит следующим образом:

{
  "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": []
        }
      ]
    }
  }
}

Этот метод имеет то преимущество, что получаемые данные полностью определяются пользовательским интерфейсом и отражают то, что хранится в БД в неизменном виде, поэтому приложение никогда не потребует обновления при добавлении дополнительных уровней в меню — будь то 2 или 20 уровней.

Однако у этого метода есть очевидный недостаток: мы теряем строгую типизацию GraphQL. Вместо элемента меню со строго типизированными полями — url как URL, label как String, objectID как ID и так далее — мы получаем простой объект, который не будет понят инструментами и клиентами GraphQL, такими как Apollo client или Relay. Следовательно, мы не сможем в полной мере воспользоваться преимуществами GraphQL.

Получение данных настроек WordPress

Ещё одна проблема возникает, когда нам нужно получить сущности, которые определяются пользовательским интерфейсом и хранятся в БД. Именно так обстоит дело с настройками в WordPress: названия параметров динамически создаются темами и плагинами, поэтому GraphQL-серверу они заранее не известны. Мета-значения также могут быть определены темами и плагинами, поэтому по умолчанию они не отображаются в схеме GraphQL.

По этой причине схема, создаваемая Gato GraphQL, не фиксирует имена параметров и их типы жёстко — доступ к ним осуществляется через поле optionValue (а также optionValues и optionObjectValue), которое принимает имя параметра и возвращает значение любого возможного встроенного типа (представленного как AnyBuiltInScalar):

type Root {
  optionValue(name: String!): AnyBuiltInScalar
}

Поскольку не все параметры предназначены для публикации через API, администратор сайта должен явно добавить их в список разрешённых — по полному имени или регулярному выражению — в настройках плагина:

Добавление параметров в список разрешённых на странице настроек
Добавление параметров в список разрешённых на странице настроек

Теперь запрос может получать параметры из белого списка:

{
  siteURL: optionValue(name: "siteurl")
  siteName: optionValue(name: "blogname")
  siteDescription: optionValue(name: "blogdescription")
}

Если приложению потребуется дополнительный параметр, его можно немедленно сделать доступным через API, просто добавив соответствующую запись в список разрешённых на странице настроек.