Input Object 'oneOf'
Das oneOf Input Object ist eine besondere Art von Input Object, bei dem genau eines der Eingabefelder als Input angegeben werden muss – andernfalls gibt der Server einen Validierungsfehler zurück. Dieses Verhalten führt Polymorphismus für Inputs in GraphQL ein und ermöglicht uns, sauberere Schemas zu entwerfen.
Zum Beispiel könnte das Abrufen eines Nutzers in unserer Anwendung über verschiedene Eigenschaften erfolgen, etwa die Nutzer-ID oder die E-Mail. Dafür müssten wir normalerweise für jede Eigenschaft ein eigenes Feld erstellen:
type Query {
userByID(id: ID!): User
userByEmail(email: String!): User
}Dank des oneOf Input Objects können wir stattdessen ein einziges Feld user haben, das alle Eigenschaften über ein oneOf Input Object UserByInput akzeptiert – in dem Wissen, dass nur eine der Eigenschaften (entweder die ID oder die E-Mail) angegeben werden kann und muss:
type Query {
user(by: UserByInput!): User
}
input UserByInput @oneOf {
id: ID
email: String
}(Beachte, dass die Syntax @oneOf oben nur zu Dokumentationszwecken im Kontext von Gato GraphQL dient, da wir keine SDL —Schema Definition Language— verwenden müssen, um das Schema zu generieren; das Plugin generiert das Schema bereits über PHP-Code, unter Verwendung der Inputs aus der Schema-Konfiguration.)
In der Query geben wir den Eingabewert für genau eine der Eigenschaften an:
{
tom: user(by: {
id: 1
}) {
name
}
jerry: user(by: {
email: "jerry@warnerbros.com"
}) {
name
}
}Wenn wir zwei (oder mehr) Werte für den Input angeben:
{
user(by: {
id: 1
email: "jerry@warnerbros.com"
}) {
name
}
}... gibt der Server einen Fehler zurück:
{
"errors": [
{
"message": "The oneOf input object 'UserByInput' must be provided exactly one value, but 2 have been provided",
"extensions": {
"type": "Query",
"field": "user(by:{id:1,email:\"jerry@warnerbros.com\"})",
"argument": "by"
}
}
],
"data": {
"user": null
}
}Wie Gato GraphQL oneOf Input Objects einsetzt
Schauen wir uns einige Situationen an, in denen das Plugin diese Funktion nutzt, und die wir auch verwenden können, um unsere GraphQL-Schemas zu erweitern.
Eine einzelne Entität über verschiedene Eigenschaften auswählen
Dies ist der allgemeine Fall der oben gezeigten Query, die den Input UserByInput im Feld user betrifft.
Immer wenn wir eine einzelne Entität (einen einzelnen User, Post, PostTag usw.) abrufen müssen, die durch mehr als eine Eigenschaft eindeutig identifiziert werden kann (z. B. per ID oder E-Mail, ID oder Slug usw.), können wir alle verschiedenen Eigenschaften in einem oneOf Input Object definieren und alle verschiedenen Felder zum Abrufen dieser Entität in einem einzigen Feld zusammenführen.
Verschiedene Datensätze in Mutations akzeptieren
Bei einer Mutation können wir verschiedene Datensätze als Inputs akzeptieren. Anstatt für jeden unterschiedlichen Datensatz verschiedene Mutation-Felder bereitzustellen, kann ein einziges Mutation-Feld durch die Verwendung eines oneOf Input Objects alle Möglichkeiten abdecken.
Zum Beispiel kann die Mutation loginUser das Einloggen von Nutzern über verschiedene Methoden unterstützen: Benutzername/Passwort, JWT-Token, Anwendungspasswörter oder andere. Deshalb erhält diese Mutation das oneOf Input Object LoginUserByInput, das derzeit die Standard-Benutzername/Passwort-Validierung von WordPress akzeptiert, aber auch auf andere Methoden erweitert werden kann:
type Mutation {
loginUser(by: LoginUserByInput!): RootLoginUserMutationPayload!
}
input LoginUserByInput @oneOf {
credentials: LoginCredentialsInput
}
input LoginCredentialsInput {
usernameOrEmail: String!
password: String!
}Meta-Werte abfragen
Das Abfragen von Meta-Werten in WordPress kann komplex sein, mit Kombinationen von Inputs, die miteinander in Konflikt geraten können, wie in der Dokumentation erklärt:
The following arguments can be passed in a key=>value paired array.
- meta_query (array) – Contains one or more arrays with the following keys:
- key (string) – Custom field key.
- value (string|array) – Custom field value. It can be an array only when compare is 'IN', 'NOT IN', 'BETWEEN', or 'NOT BETWEEN'. You don't have to specify a value when using the 'EXISTS' or 'NOT EXISTS' comparisons in WordPress 3.9 and up. (Note: Due to bug #23268, value was required for NOT EXISTS comparisons to work correctly prior to 3.9. You had to supply some string for the value parameter. An empty string or NULL will NOT work. However, any other string will do the trick and will NOT show up in your SQL when using NOT EXISTS. Need inspiration? How about 'bug #23268'.)
- compare (string) – Operator to test. Possible values are '=', '!=', '>', '>=', '<', '<=', 'LIKE', 'NOT LIKE', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN', 'EXISTS' (only in WP >= 3.5), and 'NOT EXISTS' (also only in WP >= 3.5). Values 'REGEXP', 'NOT REGEXP' and 'RLIKE' were added in WordPress 3.7. Default value is '='.
Die Dokumentation erklärt, dass value ein String oder ein Array sein kann, und je nach diesem Wert kann compare einen anderen Satz von Werten akzeptieren (z. B. IN nur für Arrays, LIKE nur für Strings). Außerdem ist value obligatorisch, aber nur wenn compare nicht EXISTS erhält – in diesem Fall wird value überhaupt nicht benötigt.
Wenn wir die verschiedenen Input-Sätze analysieren, werden wir feststellen, dass es 4 mögliche Kombinationen gibt, je nach dem angewandten Vergleich auf den Schlüssel oder den Wert und dem Typ des Werts:
keynumericValuestringValuearrayValue
Das oneOf Input Object MetaQueryCompareByInput verarbeitet diese 4 Inputs, unterstützt von verschiedenen Enums, die die möglichen Operatoren definieren, die jeder Input verwenden kann. So können wir beim Filtern nach numericValue den Operator GREATER_THAN verwenden, bei arrayValue den Operator IN und bei key den Operator EXISTS (und es ist kein value anzugeben).
Das resultierende GraphQL-Schema (mit SDL) sieht so aus:
type Query {
posts(filter: PostsFilterInput): [Post!]!
}
input PostsFilterInput {
metaQuery: [PostMetaQueryInput!]
}
input PostMetaQueryInput {
compareBy: MetaQueryCompareByInput!
key: String!
}
type MetaQueryCompareByInput @oneOf {
"""
Compare against the meta key
"""
key: MetaQueryCompareByKeyInput
"""
Compare against an array meta value
"""
array: ValueMetaQueryCompareByArrayValueInput
"""
Compare against a numeric meta value
"""
numeric: ValueMetaQueryCompareByNumericValueInput
"""
Compare against a string meta value
"""
string: ValueMetaQueryCompareByStringValueInput
}
input MetaQueryCompareByKeyInput {
operator: MetaQueryCompareByKeyOperatorEnum!
}
enum MetaQueryCompareByKeyOperatorEnum {
EXISTS
NOT_EXISTS
}
input ValueMetaQueryCompareByArrayValueInput {
operator: MetaQueryCompareByArrayValueOperatorEnum!
value: [AnyBuiltInScalar!]!
}
# AnyBuiltInScalar: Int, Float, String or Bool
scalar AnyBuiltInScalar
enum MetaQueryCompareByArrayValueOperatorEnum {
BETWEEN
IN
NOT_BETWEEN
NOT_IN
}
input ValueMetaQueryCompareByNumericValueInput {
operator: MetaQueryCompareByNumericValueOperatorEnum!
value: Numeric!
}
enum MetaQueryCompareByNumericValueOperatorEnum {
EQUALS
GREATER_THAN
GREATER_THAN_OR_EQUAL
LESS_THAN
LESS_THAN_OR_EQUAL
NOT_EQUALS
}
# Numeric: Float or Int
scalar Numeric
input ValueMetaQueryCompareByStringValueInput {
operator: MetaQueryCompareByStringValueOperatorEnum!
value: String!
}
enum MetaQueryCompareByStringValueOperatorEnum {
EQUALS
LIKE
NOT_EQUALS
NOT_LIKE
NOT_REGEXP
REGEXP
RLIKE
}Auf diese Weise wird durch die Wahl des Inputs unter compareBy die Korrektheit des gesamten Eingabedatensatzes von GraphQL validiert. Wenn wir nun Posts filtern, bei denen ein bestimmter Meta-Key existiert, können wir keinen value angeben:
{
posts(filter: {
metaQuery: {
key: "_thumbnail_id",
compareBy:{
key: {
operator: EXISTS
}
}
}
}) {
id
title
metaValue(key: "_thumbnail_id")
}
}Um Posts zu filtern, die von einem Nutzer „geliked" wurden, verwenden wir den Input arrayValue und wählen den Operator IN:
query FilterPostsLikedByUser($userID: ID!) {
posts(filter: {
metaQuery: {
key: "liked_by_users",
compareBy:{
arrayValue: {
value: $userID
operator: IN
}
}
}
}) {
id
title
}
}Introspektion: Herausfinden, ob ein Typ ein "oneOf" Input Object ist
Wir können über das Introspektionsfeld isOneOf herausfinden, ob ein Typ ein "oneOf" Input Object ist:
query IsOneOfInputObject {
__schema {
types {
name
isOneOf
}
}
}