PHP Academy · บทเรียน

ลูปเหตุการณ์ด้วย ReactPHP

เรียกใช้ I/O แบบไม่บล็อกด้วยลูปเหตุการณ์ของ ReactPHP

บทเรียน 3 จาก 413 ขั้นตอน

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

ReactPHP ในหนึ่งประโยค

ReactPHP คือชุดเครื่องมือ PHP ล้วนที่ไม่มีการพึ่งพาใด ๆ สำหรับอินพุต/เอาต์พุตแบบไม่บล็อก โดยสร้างขึ้นรอบลูปเหตุการณ์แบบเธรดเดียว ไม่ต้องใช้ส่วนขยาย — ทำงานบน PHP มาตรฐานโดยใช้ stream_select (หรือ ext-event/ext-ev เมื่อพร้อมใช้งาน) คุณสามารถใช้ชุดเครื่องมือนี้เขียนเซิร์ฟเวอร์ที่ทำงานต่อเนื่อง ไคลเอนต์ HTTP หลายรายการพร้อมกัน และกระบวนการสตรีมแบบเป็นสายด้วย PHP ทั่วไป

ลูปส่วนกลาง

ReactPHP สมัยใหม่ (v1.2+) เปิดให้ใช้ลูปเริ่มต้นผ่านส่วนเชื่อมต่อ React\EventLoop\Loop แบบ facade คงที่ — ไม่จำเป็นต้องส่งอินสแตนซ์ลูปไปทุกแห่ง คุณสามารถจัดตารางฟังก์ชันเรียกกลับด้วยตัวจับเวลา และลูปจะทำงานต่อจนไม่มีงานเหลือ

<?php
require 'vendor/autoload.php';
use React\EventLoop\Loop;

Loop::addTimer(1.0, function () {
    echo "fired after 1 second\n";
});
echo "scheduled\n";
// Loop::run() is auto-invoked at script shutdown in v1.2+

ตัวจับเวลาและงานตามช่วงเวลา

การจับเวลาแบบไม่บล็อกเข้ามาแทนที่ sleep() addTimer ทำงานครั้งเดียว ส่วน addPeriodicTimer ทำงานซ้ำ คุณสามารถยกเลิกอย่างใดอย่างหนึ่งเพื่อหยุดได้ เนื่องจากสิ่งเหล่านี้ขับเคลื่อนด้วยลูป งานอื่นจึงยังทำงานต่อได้ขณะที่ตัวจับเวลากำลังรอ

<?php
use React\EventLoop\Loop;

$count = 0;
$timer = Loop::addPeriodicTimer(0.5, function () use (&$count, &$timer) {
    echo 'tick ' . (++$count) . "\n";
    if ($count === 4) {
        Loop::cancelTimer($timer); // stop after 4 ticks
    }
});

พรอมิสสำหรับผลลัพธ์แบบอะซิงโครนัส

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

<?php
use React\Promise\Promise;

function asyncDouble(int $n): Promise {
    return new Promise(function ($resolve) use ($n) {
        \React\EventLoop\Loop::addTimer(0.1, fn() => $resolve($n * 2));
    });
}

asyncDouble(21)
    ->then(fn($r) => printf("result: %d\n", $r))   // 42
    ->catch(fn(\Throwable $e) => printf("err: %s\n", $e->getMessage()));

HTTP พร้อมกันด้วยไคลเอนต์แบบอะซิงโครนัส

ประโยชน์สำคัญคือการส่งคำขอ HTTP จำนวนมากพร้อมกันจากเธรดเดียว React\Http\Browser จะส่งคืนพรอมิส ส่วน React\Promise\all() จะรอชุดงานทั้งหมด คำขอสิบรายการที่ใช้เวลารายการละ 1 วินาทีจะเสร็จในเวลาประมาณ 1 วินาที ไม่ใช่ 10 วินาที

<?php
use React\Http\Browser;
use function React\Promise\all;

$browser = new Browser();
$urls = [
    'https://httpbin.org/delay/1',
    'https://httpbin.org/delay/1',
    'https://httpbin.org/delay/1',
];
$requests = array_map(fn($u) => $browser->get($u), $urls);

all($requests)->then(function (array $responses) {
    echo 'all done: ' . count($responses) . " responses\n";
});

ไฟเบอร์ทำให้โค้ดอ่านเหมือนทำงานแบบซิงโครนัส

สายฟังก์ชันเรียกกลับเริ่มจัดการได้ยาก ReactPHP ผสานการทำงานกับ react/async ซึ่งใช้ไฟเบอร์ของ PHP 8.1 ทำให้คุณสามารถ await() พรอมิสและเขียนโค้ดแบบเป็นลำดับตรงไปตรงมาได้ ตัวห่อหุ้ม coroutine()/async() จะเรียกใช้ฟังก์ชันของคุณภายในไฟเบอร์

<?php
use function React\Async\async;
use function React\Async\await;
use React\Http\Browser;

$main = async(function () {
    $browser = new Browser();
    $a = await($browser->get('https://httpbin.org/uuid'));
    $b = await($browser->get('https://httpbin.org/uuid'));
    echo (string) $a->getBody();
    echo (string) $b->getBody();
});
$main();

เซิร์ฟเวอร์ TCP แบบไม่บล็อก

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

<?php
use React\Socket\SocketServer;
use React\Socket\ConnectionInterface;

$socket = new SocketServer('127.0.0.1:8080');
$socket->on('connection', function (ConnectionInterface $conn) {
    $conn->write("Welcome\n");
    $conn->on('data', function ($data) use ($conn) {
        $conn->write('echo: ' . $data); // streamed back, non-blocking
    });
});
echo "listening on 127.0.0.1:8080\n";

เซิร์ฟเวอร์ HTTP ในไม่กี่บรรทัด

เซิร์ฟเวอร์ react/http จัดการคำขอด้วยฟังก์ชันเรียกกลับที่ส่งคืน Response (หรือพรอมิสของค่าดังกล่าว) เนื่องจากเป็นกระบวนการที่ทำงานต่อเนื่อง คุณจึงเริ่มต้นการพึ่งพาต่าง ๆ เพียงครั้งเดียว — แต่ก็ต้องรับผิดชอบการจัดการหน่วยความจำระหว่างคำขอด้วย

<?php
use React\Http\HttpServer;
use React\Http\Message\Response;
use Psr\Http\Message\ServerRequestInterface;
use React\Socket\SocketServer;

$http = new HttpServer(function (ServerRequestInterface $request) {
    return Response::json(['path' => $request->getUri()->getPath()]);
});
$http->listen(new SocketServer('0.0.0.0:8000'));
echo "http://0.0.0.0:8000\n";

การสตรีมโดยไม่ใช้บัฟเฟอร์

สตรีมของ ReactPHP ใช้การควบคุมแรงดันย้อนกลับ: คุณสามารถ pipe() สตรีมที่อ่านได้เข้าสู่สตรีมที่เขียนได้ แล้วสภาพแวดล้อมขณะทำงานจะจำกัดความเร็วโดยอัตโนมัติ วิธีนี้ช่วยให้คุณย้ายข้อมูลขนาดหลายกิกะไบต์โดยใช้หน่วยความจำคงที่ในปริมาณน้อย แทนการโหลดไฟล์ทั้งหมดเข้าสู่ RAM

<?php
use React\Stream\ReadableResourceStream;
use React\Stream\WritableResourceStream;

$source = new ReadableResourceStream(fopen('big.log', 'r'));
$dest   = new WritableResourceStream(fopen('php://stdout', 'w'));
$source->pipe($dest); // back-pressure aware, never buffers whole file

บาปมหันต์: การบล็อกลูป

ทุกอย่างจะพังทันทีที่คุณเรียกใช้ฟังก์ชันแบบบล็อกภายในตัวจัดการ — sleep(), PDO แบบบล็อก, curl_exec, file_get_contents กับ URL ฟังก์ชันเหล่านี้จะทำให้เธรดเดียวหยุดนิ่งและทำให้การเชื่อมต่อทั้งหมดหยุดชะงัก ให้ใช้สิ่งเทียบเท่าใน React: Loop::addTimer สำหรับการหน่วงเวลา, clue/reactphp-* หรือไดรเวอร์ฐานข้อมูลแบบอะซิงโครนัสสำหรับการค้นหา และ Browser สำหรับ HTTP

<?php
use React\EventLoop\Loop;
use function React\Async\await;
use React\Promise\Deferred;

// BAD:  sleep(2);  // freezes the entire loop
// GOOD: async delay that yields control
function delay(float $s): \React\Promise\PromiseInterface {
    $d = new Deferred();
    Loop::addTimer($s, fn() => $d->resolve(null));
    return $d->promise();
}
// await(delay(2.0)); // inside a fiber/coroutine

การจำกัดการทำงานพร้อมกัน

การกระจายงานแบบไม่จำกัดอาจใช้ตัวระบุไฟล์จนหมดหรือส่งคำขอไปยัง API ปลายทางมากเกินไป คิว clue/mq-react ของ ReactPHP จัดงานเข้าคิวโดยมีขีดจำกัดการทำงานพร้อมกัน ประมวลผลพรอมิสจำนวน N รายการในแต่ละครั้ง ขณะที่รายการที่เหลือรออยู่ วิธีนี้จะควบคุมปริมาณงานขาออกของคุณเอง

<?php
use Clue\React\Mq\Queue;
use React\Http\Browser;

$browser = new Browser();
// At most 10 in-flight requests across 1000 URLs
$q = new Queue(10, null, fn($url) => $browser->get($url));

$promises = array_map(fn($u) => $q($u), $thousandUrls ?? []);
\React\Promise\all($promises)->then(function ($responses) {
    echo 'completed ' . count($responses) . " with cap of 10\n";
});

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

คุณจะหยุดชั่วคราว 2 วินาทีภายในตัวจัดการ ReactPHP โดยไม่ทำลายการทำงานพร้อมกันได้อย่างไร

ทบทวน

ReactPHP นำการรับส่งข้อมูลแบบไม่บล็อกมาสู่ PHP แบบพื้นฐาน:

  • ลูปเหตุการณ์ เดียว (Loop ส่วนหน้า) ควบคุมตัวจับเวลา ซ็อกเก็ต และสตรีม — ไม่ต้องใช้ส่วนขยาย
  • การดำเนินการจะคืนค่าเป็น พรอมิส; all() เรียกใช้คำขอแบบขนาน และ react/async + ไฟเบอร์ช่วยให้คุณ await() เพื่อเขียนโค้ดที่ดูเหมือนทำงานแบบซิงโครนัส
  • สร้าง เซิร์ฟเวอร์ TCP/HTTP ที่ทำงานต่อเนื่อง และ สตรีมที่มีการควบคุมแรงดันย้อนกลับ ซึ่งรองรับการเชื่อมต่อหลายพันรายการต่อกระบวนการ
  • อย่าบล็อกลูปโดยเด็ดขาด — แทนที่ sleep/การรับส่งข้อมูลแบบบล็อกด้วยตัวจับเวลาของ React และไดรเวอร์แบบอะซิงโครนัส
เริ่มต้นได้ฟรี

เรียนรู้ PHP ด้วย AI tutor — ฟรี

เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป

คอร์ส
49
บทเรียน
195

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

บทเรียน “ลูปเหตุการณ์ด้วย ReactPHP” ฟรีหรือไม่

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

คุณจะเรียนรู้อะไรในบทเรียน “ลูปเหตุการณ์ด้วย ReactPHP”

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

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

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

บทเรียน “ลูปเหตุการณ์ด้วย ReactPHP” ใช้เวลานานแค่ไหน

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

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

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

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

  1. โมเดลการทำงานพร้อมกันของ PHP
  2. ไฟเบอร์ของ PHP 8.1
  3. ลูปเหตุการณ์ด้วย ReactPHP
  4. เซิร์ฟเวอร์ประสิทธิภาพสูงด้วย Swoole
← กลับไปที่ PHP Academy