Получение данных с динамической структурой
В 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 структура меню не определяется заранее — она настраивается администратором сайта через экран Меню (то есть когда не используется «блочная тема») и хранится в БД:

Это создаёт проблему: при добавлении дополнительного уровня в меню через пользовательский интерфейс необходимо также добавить дополнительный уровень в 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, просто добавив соответствующую запись в список разрешённых на странице настроек.