0Pricing
PHP Academy · บทเรียน

ตัวแก้ไข การกลายพันธุ์ และการสมัครรับข้อมูล

ดึงและเปลี่ยนข้อมูลผ่านตัวแก้ไข

ตัวแก้ไข การกลายพันธุ์ และการสมัครรับข้อมูล เป็นบทเรียน PHP Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน PHP Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส PHP Academy มีบทเรียนทั้งหมด 4 บทเรียน

รีโซลเวอร์คือที่ที่ตรรกะทำงาน

schema อธิบายว่า มีอะไรอยู่บ้าง ส่วน รีโซลเวอร์ ตัดสินใจว่า จะสร้างค่าของแต่ละฟิลด์อย่างไร รีโซลเวอร์เป็นเพียงฟังก์ชันที่เรียกใช้ได้ มิวเทชันคือรีโซลเวอร์ที่เปลี่ยนสถานะ และการสมัครรับข้อมูลใช้สตรีมค่าตามเวลา บทเรียนนี้ครอบคลุมทั้งสามเรื่อง รวมถึงรูปแบบการดำเนินการที่เชื่อมโยงทั้งหมดเข้าด้วยกัน

ลายเซ็นของรีโซลเวอร์

รีโซลเวอร์ทุกตัวจะได้รับอาร์กิวเมนต์สี่ตัว: ($objectValue, $args, $context, ResolveInfo $info)

  • $objectValue — ค่าที่รีโซลเวอร์ของค่าหลักสร้างขึ้น (ด้านบนสุดคือ rootValue)
  • $args — อาร์กิวเมนต์ของฟิลด์
  • $context — สถานะที่ใช้ร่วมกันตลอดคำขอ (ตัวจัดการฐานข้อมูล ผู้ใช้ปัจจุบัน)
  • $info — ข้อมูลเมตาของ AST/ฟิลด์ (ชื่อฟิลด์ ชุดการเลือก เส้นทาง)
<?php
use GraphQL\Type\Definition\ResolveInfo;

$resolve = function ($objectValue, array $args, $context, ResolveInfo $info) {
    // $context['db'], $context['user'] set up per request
    return $context['db']->find($args['id']);
};

รีโซลเวอร์เริ่มต้น

หากคุณไม่ระบุ resolve รีโซลเวอร์เริ่มต้นของ graphql-php จะอ่านชื่อฟิลด์จากค่าหลัก ซึ่งอาจเป็นคีย์อาร์เรย์ พร็อพเพอร์ตีสาธารณะ หรือเมธอด get<Field>() นั่นหมายความว่าโดยมากคุณสามารถรีโซลฟ์ชนิดออบเจ็กต์ทั้งหมดได้โดยไม่ต้องเขียนโค้ดประกอบซ้ำ เพียงคืนอาร์เรย์ธรรมดาหรือออบเจ็กต์ถ่ายโอนข้อมูลจากค่าหลัก

<?php
// Parent returns this array; child fields resolve by key automatically:
$user = [
    'id' => 1,
    'name' => 'Ada',
    'email' => 'ada@example.com',
];
// 'name' field -> $user['name'] with no explicit resolver needed
var_dump($user['name']);

รีโซลเวอร์ส่งต่อจากค่าหลักไปยังค่าลูก

การดำเนินการทำจากบนลงล่าง: รีโซลเวอร์ Query.user คืนค่าผู้ใช้ ซึ่งจะกลายเป็น $objectValue สำหรับ User.posts และผลลัพธ์ของมันจะกลายเป็นค่าหลักของ Post.title แต่ละรายการ การเข้าใจการส่งต่อนี้เป็นสิ่งสำคัญ เพราะตรงนี้เองที่ปัญหา N+1 ปรากฏขึ้น (จะกล่าวถึงในบทเรียนถัดไป)

<?php
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;

$userType = new ObjectType([
    'name' => 'User',
    'fields' => fn() => [
        'id'    => Type::id(),
        'name'  => Type::string(),
        'posts' => [
            'type' => Type::listOf(Type::string()),
            // $user is the parent value resolved by Query.user
            'resolve' => fn($user) => Posts::titlesForUser($user['id']),
        ],
    ],
]);

การคืนพรอมิส (แบบอะซิงโครนัส)

รีโซลเวอร์อาจคืนค่า หรือ พรอมิสก็ได้ graphql-php มีตัวปรับพรอมิสแบบทำงานพร้อมกันให้ใช้ และเมื่อใช้ตัวปรับของ ReactPHP/Amp ก็สามารถเลื่อนการรีโซลฟ์และจัดงานเป็นชุดได้ แม้ทำงานแบบพร้อมกัน การคืนออบเจ็กต์ Deferred ก็ทำให้ตัวดำเนินการรวบรวมงานไว้ แล้วเรียกใช้หลังจากระดับการรีโซลฟ์ปัจจุบันเสร็จสิ้น ซึ่งเป็นกลไกพื้นฐานที่ DataLoader ใช้

<?php
use GraphQL\Deferred;

$resolve = function ($post) use ($authorBuffer) {
    $authorBuffer->add($post['author_id']);   // queue the id
    return new Deferred(function () use ($authorBuffer, $post) {
        $authorBuffer->loadOnce();             // one batched query
        return $authorBuffer->get($post['author_id']);
    });
};

มิวเทชันเปลี่ยนสถานะ

มิวเทชัน เป็นเพียงชนิดรากที่ชื่อ Mutation ตามแบบแผน ฟิลด์ระดับบนสุดของมิวเทชันจะทำงานตามลำดับ ไม่ใช่ทำงานพร้อมกัน เพื่อให้ผลข้างเคียงเกิดตามลำดับ อินพุตมักถูกรวมไว้ใน InputObjectType เพื่อให้ลายเซ็นกระชับและชัดเจน

<?php
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\Type;

$createPostInput = new InputObjectType([
    'name' => 'CreatePostInput',
    'fields' => [
        'title' => Type::nonNull(Type::string()),
        'body'  => Type::string(),
    ],
]);

การเชื่อมต่อชนิดมิวเทชัน

ฟิลด์มิวเทชันรับออบเจ็กต์อินพุตเป็นอาร์กิวเมนต์ และคืนเอนทิตีที่สร้างขึ้น เพื่อให้ไคลเอ็นต์อ่านฟิลด์กลับมาได้ภายในการเรียกครั้งเดียวกัน ให้ตรวจสอบความถูกต้องและสิทธิ์ภายในรีโซลเวอร์ และโยนข้อผิดพลาดเมื่อไม่ผ่าน

<?php
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;

$mutationType = new ObjectType([
    'name' => 'Mutation',
    'fields' => [
        'createPost' => [
            'type' => $postType,
            'args' => ['input' => Type::nonNull($createPostInput)],
            'resolve' => function ($root, array $args, $context) {
                if (!$context['user']) {
                    throw new \RuntimeException('Unauthenticated');
                }
                return PostRepo::create($args['input'], $context['user']);
            },
        ],
    ],
]);

ข้อผิดพลาด: ปลอดภัยสำหรับไคลเอ็นต์กับภายใน

โดยค่าเริ่มต้น graphql-php จะซ่อนข้อความข้อยกเว้น และแสดง Internal server error เพื่อป้องกันไม่ให้ข้อมูลภายในรั่วไหล หากต้องการส่งข้อความให้ไคลเอ็นต์เห็น ให้ใช้งาน GraphQL\Error\ClientAware และคืนค่า true จาก isClientSafe() เพิ่มรหัสที่เครื่องอ่านได้ผ่าน extensions

<?php
use GraphQL\Error\ClientAware;

class ValidationError extends \RuntimeException implements ClientAware {
    public function isClientSafe(): bool { return true; }
    // older versions also used getCategory(): string
}

การสมัครรับข้อมูล: แนวคิด

ชนิดราก การสมัครรับข้อมูล เปิดให้ไคลเอ็นต์รับผลลัพธ์เป็นสตรีมเมื่อเกิดเหตุการณ์ เช่น มีข้อความใหม่หรือราคาขยับ ข้อกำหนด GraphQL กำหนด ความหมายเชิงพฤติกรรม ของการสมัครรับข้อมูล แต่ graphql-php จะดำเนินการเพียงหนึ่งการดำเนินการต่อการเรียกใช้เท่านั้น ไม่ได้ทำหน้าที่เป็นเซิร์ฟเวอร์ซ็อกเก็ตที่ทำงานต่อเนื่องยาวนานด้วยตัวเอง คุณต้องจัดเตรียมชั้นการส่งข้อมูลเอง

  • graphql-php จะรีโซลฟ์ เพย์โหลด ของการสมัครรับข้อมูลสำหรับแต่ละเหตุการณ์ที่คุณส่งเข้ามา
  • ชั้นการส่งข้อมูล (WebSocket ผ่าน Ratchet/Mercure/Pusher) จะส่งเหตุการณ์ไปยังไคลเอ็นต์

รูปแบบรีโซลเวอร์ของการสมัครรับข้อมูล

ในทางปฏิบัติ คุณจะแบ่งการสมัครรับข้อมูลออกเป็นขั้นตอน ลงทะเบียนความสนใจ (ลงทะเบียนความสนใจและคืนแหล่งเหตุการณ์) และขั้นตอน รีโซลฟ์ (แปลงแต่ละเหตุการณ์เป็นเพย์โหลดของฟิลด์) สแตก PHP จำนวนมากใช้ graphql-php ร่วมกับ Mercure หรือโบรกเกอร์เผยแพร่/สมัครรับข้อมูล รีโซลเวอร์ด้านล่างแสดงการแมปต่อเหตุการณ์ที่ graphql-php รับผิดชอบ

<?php
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;

$subscriptionType = new ObjectType([
    'name' => 'Subscription',
    'fields' => [
        'messageAdded' => [
            'type' => $messageType,
            'args' => ['channelId' => Type::nonNull(Type::id())],
            // graphql-php resolves each pushed event into the payload;
            // a WebSocket/Mercure transport drives when this runs.
            'resolve' => fn($event) => $event['message'],
        ],
    ],
]);

บริบทคือช่องทางสำหรับการยืนยันตัวตนและ DI

อาร์กิวเมนต์ตัวที่สามของรีโซลเวอร์คือ $context ซึ่งสร้างขึ้นครั้งเดียวต่อคำขอและส่งต่อเข้าไปในรีโซลเวอร์ทุกตัว บริบทเป็นตำแหน่งที่เหมาะสมสำหรับผู้ใช้ที่ผ่านการยืนยันตัวตน การเชื่อมต่อฐานข้อมูล และ DataLoaders ของคุณ การรวมการยืนยันตัวตนไว้ที่เดียวทำให้รีโซลเวอร์กระชับ เพราะรีโซลเวอร์เพียงถามบริบทว่าผู้ใช้คือใคร แทนที่จะคำนวณข้อมูลดังกล่าวขึ้นใหม่

<?php
require 'vendor/autoload.php';

// Built once per HTTP request, passed to executeQuery():
$context = [
    'user' => authenticate($_SERVER['HTTP_AUTHORIZATION'] ?? ''),
    'db'   => $pdo,
];

$resolve = function ($root, array $args, array $context) {
    if ($context['user'] === null) {
        throw new \RuntimeException('Unauthenticated');
    }
    return $context['db']->find($args['id']);
};

ตรวจสอบความเข้าใจ

คุณจะทำให้ข้อความข้อยกเว้นแสดงต่อไคลเอ็นต์ GraphQL ได้อย่างไร

ทบทวน

คุณได้เรียนรู้แก่นของการดำเนินการใน GraphQL:

  • รีโซลเวอร์รับ ($value, $args, $context, $info) และรีโซลเวอร์เริ่มต้นจะอ่านคีย์/ตัวอ่านค่าจากค่าหลัก
  • การรีโซลฟ์ส่งต่อจากค่าหลักไปยังค่าลูก ซึ่งเป็นต้นเหตุของ N+1
  • การคืน Deferred/พรอมิสช่วยให้จัดงานเป็นชุดได้
  • มิวเทชันคือฟิลด์รากที่ทำงานตามลำดับและใช้ InputObjectType โดยให้ตรวจสอบสิทธิ์/ความถูกต้องในรีโซลเวอร์
  • การสมัครรับข้อมูลกำหนดการรีโซลฟ์เพย์โหลด ส่วนคุณเป็นผู้จัดเตรียมชั้นการส่งข้อมูล
  • ClientAware ควบคุมว่าข้อความข้อผิดพลาดใดที่ไคลเอ็นต์มองเห็นได้

ถัดไป: แก้ปัญหา N+1 ด้วย DataLoader

คำถามที่พบบ่อย

บทเรียน “ตัวแก้ไข การกลายพันธุ์ และการสมัครรับข้อมูล” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “ตัวแก้ไข การกลายพันธุ์ และการสมัครรับข้อมูล” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส PHP Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส PHP Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “ตัวแก้ไข การกลายพันธุ์ และการสมัครรับข้อมูล”

ดึงและเปลี่ยนข้อมูลผ่านตัวแก้ไข คุณปฏิบัติ PHP Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน PHP Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน PHP Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน

บทเรียน “ตัวแก้ไข การกลายพันธุ์ และการสมัครรับข้อมูล” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน PHP Academy นี้ได้ไหม

ได้ บทเรียน PHP Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. GraphQL เทียบกับ REST
  2. การสร้างสคีมาด้วย graphql-php
  3. ตัวแก้ไข การกลายพันธุ์ และการสมัครรับข้อมูล
  4. ประสิทธิภาพ: N+1 และ DataLoader
← กลับไปที่ PHP Academy