Kavramlar, Fikirler, Stratejiler
Kavramlar, Fikirler, StratejilerMeta direktifler aracılığıyla scripting yetenekleri

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:

  1. [String] türündeki tekil girdiyi String türünde girdiler dizisine dönüştürme
  2. Bu dizideki öğeleri yineleyerek her biri için, ardından String türünde bir girdi alacak olan aşağı akış direktifini (@strTitleCase) çağırma ve uygulama
  3. String değ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:

Başka bir direktifi değiştirmek için @underEachArrayItem ekleme

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")
  }
}