Meta direktifler aracılığıyla scripting yetenekleri
Diyelim ki sorguda alana uygulanabilen ve değerini "hello world!" ifadesinden "Hello World!" ifadesine dönüştüren bir @strTitleCase direktifimiz var; bu nedenle yalnızca String türündeki alanlara uygulamak mantıklıdır.
Bu query çalıştırıldığında:
{
post(by: { id: 1 }) {
title @strTitleCase
}
}...şu sonuç üretilecektir:
{
"data": {
"post": {
"title": "Hello World!"
}
}
}Şimdi, alan türünün bu örnekte olduğu gibi [String] (veya [String!]) olduğunu varsayalım:
type Post {
categoryNames: [String!]
}Bu query çalıştırılırken categoryNames alanına @strTitleCase direktifi uygulandığında ne olmalıdır?
{
post(by: { id: 1 }) {
categoryNames @strTitleCase
}
}İdeal olarak, yanıt dizi içindeki her String değerinin dönüştürülmüş hali olacaktır:
{
"data": {
"post": {
"categoryNames": [
"Software",
"Web Development",
"Mobile App"
]
}
}
}Bunun gerçekleşebilmesi için @strTitleCase direktif çözümleyicisinin girdinin bir dizi olup olmadığını kontrol etmesi ve buna göre ilerlemesi gerekecektir (bu PHP kodu bir örnektir, eklentideki gerçek yöntem farklıdır):
function applyDirective(mixed $value, array $schemaDef): mixed
{
// Convert each item in an array to title case
if ($schemaDef['isArray']) {
return array_map(ucwords(...), $value);
}
// Convert the String value to title case
return ucwords($value);
}Bu çok zor değil. Peki ya alan bir String dizisinin dizisi, yani [[String]] ise? Biraz daha zor olsa da direktif bununla da başa çıkabilir:
function applyDirective(mixed $value, array $schemaDef): mixed
{
// Convert each item in an array of arrays to title case
if ($schemaDef['isArrayOfArrays']) {
return array_map(
fn (array $array) => array_map(ucwords(...), $array),
$value
);
}
// Convert each item in an array to title case
if ($schemaDef['isArray']) {
return array_map(ucwords(...), $value);
}
// Convert the String value to title case
return ucwords($value);
}Peki ya [[[String]]] veya [[[[String]]]] ise? Uygulamak giderek zorlaşmaya başlar.
Daha da kötüsü, bu ek mantık şablonu kodu, dizilere uygulanabilecek her direktif için uygulanmak zorunda kalacaktır. Örneğin, bir @strUpperCase direktifi uygulamak için bu fazladan mantık da gerekli olacaktır:
function applyDirective(mixed $value, array $schemaDef): mixed
{
// Convert each item in an array of arrays to uppercase
if ($schemaDef['isArrayOfArrays']) {
return array_map(
fn (array $array) => array_map(strtoupper(...), $array),
$value
);
}
// Convert each item in an array to uppercase
if ($schemaDef['isArray']) {
return array_map(strtoupper(...), $value);
}
// Convert the String value to uppercase
return strtoupper($value);
}Pek güzel görünmüyor, değil mi?
Çözüm: bir direktifin girdisini başka bir direktif aracılığıyla değiştirmek
İşte burada, bir direktifi başka bir direktifin davranışını değiştirmek için uygulamak işe yarayabilir.
Alan için her olası dizi üssüyle (yani String, [String], [[String]], [[[String]]] vb.) uğraşmak yerine, @strTitleCase yalnızca temel durum olan String ile ilgilenebilir:
function applyDirective(mixed $value, array $schemaDef): mixed
{
// The input will always be `String`
// Convert the String value to title case
return ucwords($value);
}Ardından, başka bir direktif olan @underEachArrayItem şu işlemleri yaparak davranışını değiştirebilir:
[String]türündeki tekil girdiyiStringtüründe girdiler dizisine dönüştürme- Bu dizideki öğeleri yineleyerek her biri için, ardından
Stringtüründe bir girdi alacak olan aşağı akış direktifini (@strTitleCase) çağırma ve uygulama Stringdeğerleri dizisini tekrar tek bir[String]değerine dönüştürme
Daha sonra bu query çalıştırılabilir:
{
post(by: { id: 1 }) {
categoryNames @underEachArrayItem @strTitleCase
}
}Bu gif, @underEachArrayItem direktifini çalışırken göstermektedir:

Bu çözümün güzelliği, dizinin derinliğini direktifin uygulanmasından ayırmasıdır. Girdi [[String]] türündeyse, tek yapmamız gereken, hedeflenen direktifi değiştiren @underEachArrayItem direktifini değiştirecek olan ek bir @underEachArrayItem eklemektir:
{
customerAllNames @underEachArrayItem @underEachArrayItem @strTitleCase
}...şu sonucu üretir:
{
"data": {
"customerAllNames": [
[
"John",
"Edward",
"Stevenson"
],
[
"Samantha",
"Perkins"
],
[
"Michael",
"Edward",
"Higgs"
]
]
}
}Görüldüğü üzere, bir direktifin başka bir direktifi değiştirmesi, bir direktif pipeline'ında da gerçekleşebilir; bu pipeline'da direktiflerden biri aşağı akış direktifini etkilerken kendileri de bir yukarı akış direktifi tarafından değiştirilir.
@underEachArrayItem direktifini bir "meta-direktif" olarak adlandırıyoruz: başka bir direktifin davranışını değiştiren bir direktif. Bunu yaparak, geliştiriciye GraphQL query içine programlama mantığı eklemek için "meta-scripting" yetenekleri kazandırır.
GraphQL query'sini biçimlendirme
Boşluklar anlamsal değer katmadığından query ve SDL'yi iç içe geçmeyi daha iyi aktarmak için biçimlendirebiliriz:
{
customerAllNames
@underEachArrayItem
@underEachArrayItem
@strTitleCase
}İç içe direktiflerden oluşan bir pipeline tanımlama
@underEachArrayItem direktifi, @strTitleCase direktifinin davranışını değiştirmesi gerektiğini nasıl bilir? Önceki örnekte bunun nedeni, hemen önünde konumlandırılmış olmasıydı. Peki hemen ardından başka bir direktif daha geldiğinde ne olmalıdır?
Örneğin, bu query'de:
{
post(by: { id: 1 }) {
categoryNames
@underEachArrayItem
@strTitleCase
@strTranslate(to: "es")
}
}...@underEachArrayItem, @strTranslate direktifinin davranışını da değiştirmelidir; çünkü bu direktif de bir String üzerine uygulanmalıdır ve şu yanıtı üretir:
{
"data": {
"post": {
"categoryNames": [
"Software",
"Desarrollo web",
"Aplicación movil"
]
}
}
}Ancak, sonrasına yerleştirilen bir direktifin diziye uygulanması gerekebilir; tekil String değerine değil. Örneğin, aşağıdaki @arrayPad direktifi, bir diziye varsayılan değerlerle eksik girişler ekler; dolayısıyla @underEachArrayItem tarafından etkilenmemelidir:
{
post(by: { id: 1 }) {
categoryNames
@underEachArrayItem
@strTitleCase
@arrayPad(length: 5, value: "undefined")
}
}...şu yanıtı üretir:
{
"data": {
"post": {
"categoryNames": [
"Software",
"Web Development",
"Mobile App",
"undefined",
"undefined"
]
}
}
}İki durum arasındaki ayrımı yapmak için @underEachArrayItem direktifine affectDirectivesUnderPos argümanını ekliyoruz; bu argüman, etkilenmesi gereken direktiflerin göreli konumunu bir Int dizisi olarak tanımlar.
Aşağıdaki query'de, @underEachArrayItem direktifi @strTitleCase ve @strTranslate direktiflerine uygulanması gerektiğini bilir; çünkü bunlar kendisinden göreli 1 ve 2 konumlarında yer almaktadır:
{
post(by: { id: 1 }) {
categoryNames
@underEachArrayItem(affectDirectivesUnderPos: [1, 2])
@strTitleCase
@strTranslate(to: "es")
}
}Bu diğer query'de ise @underEachArrayItem, yalnızca @strTitleCase direktifine (göreli konum 1) uygulanır; @arrayPad direktifine uygulanmaz:
{
post(by: { id: 1 }) {
categoryNames
@underEachArrayItem(affectDirectivesUnderPos: [1])
@strTitleCase
@arrayPad(length: 5, value: "undefined")
}
}affectDirectivesUnderPos için varsayılan değer [1] olarak ayarlanmıştır; dolayısıyla belirtilmezse direktif her zaman hemen ardındaki direktife uygulanacaktır. Yukarıdaki query bu nedenle şuna eşdeğerdir:
{
post(by: { id: 1 }) {
categoryNames
@underEachArrayItem
@strTitleCase
@arrayPad(length: 5, value: "undefined")
}
}Meta direktif tarafından etkilenen ve etkilenmeyen direktiflerin herhangi bir kombinasyonunu tanımlayabiliriz:
{
post(by: { id: 1 }) {
categoryNames
@underEachArrayItem(affectDirectivesUnderPos: [1, 2])
@strTitleCase
@strTranslate(to: "es")
@arrayPad(length: 5, value: "undefined")
}
}