ความสามารถด้านการเขียนสคริปต์ผ่านเมตาไดเรกทีฟ
สมมติว่าเรามีไดเรกทีฟ @strTitleCase ที่สามารถใช้กับฟิลด์ในคิวรีได้ โดยแปลงค่าจาก "hello world!" เป็น "Hello World!" ดังนั้นจึงสมเหตุสมผลที่จะใช้กับฟิลด์ประเภท String เท่านั้น
เมื่อรันคิวรีนี้:
{
post(by: { id: 1 }) {
title @strTitleCase
}
}...จะได้ผลลัพธ์:
{
"data": {
"post": {
"title": "Hello World!"
}
}
}ทีนี้ สมมติว่าประเภทของฟิลด์คือ [String] (หรือ [String!]) ดังในกรณีนี้:
type Post {
categoryNames: [String!]
}ควรจะเกิดอะไรขึ้นเมื่อนำไดเรกทีฟ @strTitleCase มาใช้กับฟิลด์ categoryNames เมื่อรันคิวรีนี้?
{
post(by: { id: 1 }) {
categoryNames @strTitleCase
}
}ในอุดมคติ ผลลัพธ์ควรเป็นการแปลงค่า String ทุกค่าภายในอาร์เรย์:
{
"data": {
"post": {
"categoryNames": [
"Software",
"Web Development",
"Mobile App"
]
}
}
}เพื่อให้เป็นเช่นนั้น ไดเรกทีฟ resolver ของ @strTitleCase จะต้องตรวจสอบว่าอินพุตเป็นอาร์เรย์หรือไม่ แล้วดำเนินการตามนั้น (โค้ด PHP นี้เป็นเพียงตัวอย่าง เมธอดจริงในปลั๊กอินจะแตกต่างกัน):
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);
}สิ่งนี้ไม่ยากนัก แต่ถ้าฟิลด์เป็นอาร์เรย์ของอาร์เรย์ของ String นั่นคือ [[String]] จะเป็นอย่างไร? แม้จะยากขึ้นเล็กน้อย ไดเรกทีฟก็สามารถจัดการได้เช่นกัน:
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);
}แล้วถ้าเป็น [[[String]]] หรือ [[[[String]]]] ล่ะ? มันเริ่มยากในการนำไปใช้งาน
ยิ่งไปกว่านั้น โค้ดโครงสร้าง (boilerplate) ของลอจิกเพิ่มเติมนี้จะต้องถูกนำไปใช้ในทุกไดเรกทีฟที่อาจใช้กับอาร์เรย์ ตัวอย่างเช่น การนำไดเรกทีฟ @strUpperCase ไปใช้ ก็ต้องการลอจิกเพิ่มเติมนี้ด้วย:
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);
}ดูไม่ค่อยสวยงามเท่าไหร่ ใช่ไหม?
แนวทางแก้ไข: การปรับเปลี่ยนอินพุตของไดเรกทีฟผ่านไดเรกทีฟอื่น
นี่คือจุดที่การนำไดเรกทีฟมาปรับเปลี่ยนพฤติกรรมของไดเรกทีฟอื่นจะเป็นประโยชน์
แทนที่จะจัดการกับทุกระดับของอาร์เรย์สำหรับฟิลด์ (นั่นคือ String, [String], [[String]], [[[String]]] เป็นต้น) @strTitleCase สามารถจัดการเฉพาะกรณีพื้นฐาน String เท่านั้น:
function applyDirective(mixed $value, array $schemaDef): mixed
{
// The input will always be `String`
// Convert the String value to title case
return ucwords($value);
}จากนั้น ไดเรกทีฟอีกตัวหนึ่งคือ @underEachArrayItem สามารถปรับเปลี่ยนพฤติกรรมได้โดย:
- แปลงอินพุตเดี่ยวประเภท
[String]เป็นอาร์เรย์ของอินพุตประเภทString - วนซ้ำผ่านรายการในอาร์เรย์นี้ และสำหรับแต่ละรายการ เรียกและนำไดเรกทีฟ downstream (
@strTitleCase) มาใช้ ซึ่งจะได้รับอินพุตประเภทString - แปลงอาร์เรย์ของค่า
Stringกลับเป็นค่า[String]เดี่ยว
จากนั้นเราสามารถรันคิวรีนี้ได้:
{
post(by: { id: 1 }) {
categoryNames @underEachArrayItem @strTitleCase
}
}GIF นี้แสดงการทำงานของ @underEachArrayItem:

ข้อดีของแนวทางนี้คือมันแยกความลึกของอาร์เรย์ออกจากการนำไดเรกทีฟไปใช้ หากอินพุตเป็นประเภท [[String]] สิ่งที่ต้องทำคือเพิ่ม @underEachArrayItem อีกตัว ซึ่งจะปรับเปลี่ยน @underEachArrayItem ที่ปรับเปลี่ยนไดเรกทีฟที่ต้องการ:
{
customerAllNames @underEachArrayItem @underEachArrayItem @strTitleCase
}...ซึ่งได้ผลลัพธ์:
{
"data": {
"customerAllNames": [
[
"John",
"Edward",
"Stevenson"
],
[
"Samantha",
"Perkins"
],
[
"Michael",
"Edward",
"Higgs"
]
]
}
}ดังนั้น ดังที่เราเห็น การที่ไดเรกทีฟปรับเปลี่ยนไดเรกทีฟอื่นสามารถเกิดขึ้นใน pipeline ของไดเรกทีฟได้เช่นกัน โดยหนึ่งในนั้นส่งผลต่อไดเรกทีฟ downstream และตัวเองก็ถูกปรับเปลี่ยนโดยไดเรกทีฟ upstream
เราเรียก @underEachArrayItem ว่า "เมตาไดเรกทีฟ": ไดเรกทีฟที่ปรับเปลี่ยนพฤติกรรมของไดเรกทีฟอื่น ด้วยการทำเช่นนี้ มันมอบความสามารถ "เมตา-สคริปติ้ง" ให้กับนักพัฒนา เพื่อเพิ่มลอจิกการโปรแกรมภายใน GraphQL queries
การจัดรูปแบบ GraphQL queries
เนื่องจากช่องว่างไม่มีค่าทางความหมาย เราสามารถจัดรูปแบบคิวรีและ SDL เพื่อแสดงการซ้อนกันให้ชัดเจนขึ้น:
{
customerAllNames
@underEachArrayItem
@underEachArrayItem
@strTitleCase
}การกำหนด pipeline ของไดเรกทีฟที่ซ้อนกัน
@underEachArrayItem รู้ได้อย่างไรว่าต้องปรับเปลี่ยนพฤติกรรมของ @strTitleCase? ในตัวอย่างก่อนหน้า เป็นเพราะมันถูกวางไว้ก่อนหน้าโดยตรง แต่ควรจะเกิดอะไรขึ้นเมื่อมีไดเรกทีฟอื่นอยู่หลังจากนั้น?
ตัวอย่างเช่น ในคิวรีนี้:
{
post(by: { id: 1 }) {
categoryNames
@underEachArrayItem
@strTitleCase
@strTranslate(to: "es")
}
}...@underEachArrayItem ควรปรับเปลี่ยนพฤติกรรมของไดเรกทีฟ @strTranslate ด้วย เนื่องจากไดเรกทีฟนี้ต้องถูกนำไปใช้กับ String เช่นกัน ทำให้ได้ผลลัพธ์นี้:
{
"data": {
"post": {
"categoryNames": [
"Software",
"Desarrollo web",
"Aplicación movil"
]
}
}
}อย่างไรก็ตาม ไดเรกทีฟที่วางไว้ภายหลังอาจต้องถูกนำไปใช้กับอาร์เรย์ ไม่ใช่กับค่า String แต่ละค่า ตัวอย่างเช่น ไดเรกทีฟ @arrayPad ด้านล่างเติมรายการที่ขาดหายในอาร์เรย์ด้วยค่าเริ่มต้น ดังนั้นจึงไม่ควรได้รับผลกระทบจาก @underEachArrayItem:
{
post(by: { id: 1 }) {
categoryNames
@underEachArrayItem
@strTitleCase
@arrayPad(length: 5, value: "undefined")
}
}...ซึ่งได้ผลลัพธ์นี้:
{
"data": {
"post": {
"categoryNames": [
"Software",
"Web Development",
"Mobile App",
"undefined",
"undefined"
]
}
}
}เพื่อแยกความแตกต่างระหว่างสองสถานการณ์นี้ เราแนะนำอาร์กิวเมนต์ affectDirectivesUnderPos ให้กับ @underEachArrayItem ซึ่งกำหนดตำแหน่งสัมพัทธ์ของไดเรกทีฟที่ต้องได้รับผลกระทบ เป็นอาร์เรย์ของ Int
ในคิวรีด้านล่าง @underEachArrayItem รู้ว่าต้องนำไปใช้กับ @strTitleCase และ @strTranslate เนื่องจากอยู่ในตำแหน่งสัมพัทธ์ 1 และ 2 จากตัวมันเอง:
{
post(by: { id: 1 }) {
categoryNames
@underEachArrayItem(affectDirectivesUnderPos: [1, 2])
@strTitleCase
@strTranslate(to: "es")
}
}ในคิวรีอื่นนี้ @underEachArrayItem ถูกนำไปใช้เฉพาะกับ @strTitleCase (ตำแหน่งสัมพัทธ์ 1) แต่ไม่ใช้กับ @arrayPad:
{
post(by: { id: 1 }) {
categoryNames
@underEachArrayItem(affectDirectivesUnderPos: [1])
@strTitleCase
@arrayPad(length: 5, value: "undefined")
}
}ค่าเริ่มต้นของ affectDirectivesUnderPos ถูกตั้งไว้ที่ [1] ดังนั้นหากไม่ระบุ ไดเรกทีฟจะถูกนำไปใช้กับไดเรกทีฟถัดไปโดยตรงเสมอ คิวรีข้างต้นจึงเทียบเท่ากับคิวรีนี้:
{
post(by: { id: 1 }) {
categoryNames
@underEachArrayItem
@strTitleCase
@arrayPad(length: 5, value: "undefined")
}
}เราสามารถกำหนดการรวมกันใดๆ ของไดเรกทีฟที่ได้รับผลกระทบจากเมตาไดเรกทีฟและไม่ได้รับผลกระทบ:
{
post(by: { id: 1 }) {
categoryNames
@underEachArrayItem(affectDirectivesUnderPos: [1, 2])
@strTitleCase
@strTranslate(to: "es")
@arrayPad(length: 5, value: "undefined")
}
}