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

อธิบายพอร์ตและอะแดปเตอร์

แยกแกนหลักด้วยพอร์ตและอะแดปเตอร์ที่ถอดเปลี่ยนได้

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

แนวคิดรูปหกเหลี่ยม

พอร์ตและอะแดปเตอร์ — สถาปัตยกรรมหกเหลี่ยมของ Alistair Cockburn — วาดภาพแอปพลิเคชันของคุณเป็นรูปหกเหลี่ยม ด้านในคือ ตรรกะทางธุรกิจล้วน การโต้ตอบกับโลกภายนอกทุกอย่าง (เอชทีทีพี ฐานข้อมูล คิว นาฬิกา อีเมล) ต้องข้ามผ่านพอร์ต และแต่ละพอร์ตจะมีอะแดปเตอร์อย่างน้อยหนึ่งตัวมารองรับ รูปทรงนี้ไม่มีด้านบนหรือด้านล่างที่มีสิทธิพิเศษ ส่วนติดต่อผู้ใช้และฐานข้อมูลจึงมีสถานะสมมาตรกัน ทั้งคู่เป็นเพียงอะแดปเตอร์

พอร์ตคือส่วนต่อประสาน

พอร์ตคือส่วนต่อประสานที่แกนกลางของแอปพลิเคชันเป็นเจ้าของ ใช้แสดงความต้องการหรือความสามารถในภาษาของโดเมน พอร์ตต้องไม่ทำให้คำศัพท์ของโครงสร้างพื้นฐานเล็ดลอดเข้ามา — ไม่มี PDOStatement ไม่มี GuzzleResponse และไม่มีอีโลเควนต์

<?php
// Driven (outbound) port: the core needs to persist users
interface UserRepository
{
    public function byId(UserId $id): ?User;
    public function save(User $user): void;
}

พอร์ตขับเคลื่อนและพอร์ตถูกขับเคลื่อน

พอร์ตมีอยู่สองรูปแบบ:

  • พอร์ตขับเคลื่อน (หลัก ขาเข้า) — ส่วนต่อประสานที่โลกภายนอกเรียกเพื่อขับเคลื่อนแอป โดยทั่วไปคือส่วนต่อประสานของกรณีใช้งาน
  • พอร์ตถูกขับเคลื่อน (รอง ขาออก) — ส่วนต่อประสานที่แอปเรียกเพื่อเข้าถึงโลกภายนอก เช่น คลังข้อมูล ระบบส่งจดหมาย และนาฬิกา

อะแดปเตอร์ขับเคลื่อนจะเรียกเข้าสู่แกนกลาง ส่วนแกนกลางจะเรียกออกผ่านอะแดปเตอร์ที่ถูกขับเคลื่อน

<?php
// Driving (inbound) port — the public capability of the core
interface RegisterUser
{
    public function handle(string $email, string $plainPassword): UserId;
}

แกนกลางเป็นผู้ใช้งานพอร์ตขับเคลื่อน

กรณีใช้งานใช้งานพอร์ตขับเคลื่อนและพึ่งพาพอร์ตที่ถูกขับเคลื่อน โปรดสังเกตว่า กรณีใช้งานรับ PasswordHasher และ Clock เป็นพอร์ตที่ฉีดเข้ามา — แม้แต่เวลาและการแฮชก็ถูกทำให้เป็นนามธรรม เพื่อให้แกนกลางทำงานได้อย่างแน่นอนและทดสอบได้

<?php
final class RegisterUserService implements RegisterUser
{
    public function __construct(
        private UserRepository $users,
        private PasswordHasher $hasher,
        private Clock $clock,
    ) {}

    public function handle(string $email, string $plain): UserId {
        if ($this->users->byEmail($email)) {
            throw new EmailAlreadyTaken($email);
        }
        $user = User::register(
            $email,
            $this->hasher->hash($plain),
            $this->clock->now()
        );
        $this->users->save($user);
        return $user->id();
    }
}

อะแดปเตอร์ที่ถูกขับเคลื่อน

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

<?php
final class PdoUserRepository implements UserRepository
{
    public function __construct(private PDO $pdo) {}

    public function byId(UserId $id): ?User {
        $stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = ?');
        $stmt->execute([(string) $id]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        return $row ? User::fromRow($row) : null;
    }
    public function save(User $user): void {
        // INSERT ... ON CONFLICT UPDATE
    }
}

อะแดปเตอร์ขับเคลื่อน

อะแดปเตอร์ขับเคลื่อนแปลงตัวกระตุ้นจากภายนอกให้เป็นการเรียกพอร์ตขับเคลื่อน ตัวควบคุมเอชทีทีพี คำสั่ง CLI และตัวรับข้อความ ล้วนเป็นอะแดปเตอร์ขับเคลื่อนที่สับเปลี่ยนกันได้สำหรับกรณีใช้งานเดียวกัน

<?php
// CLI driving adapter
final class RegisterUserCommand
{
    public function __construct(private RegisterUser $register) {}

    public function run(array $argv): int {
        [$email, $password] = array_slice($argv, 1);
        $id = $this->register->handle($email, $password);
        fwrite(STDOUT, "Created user $id\n");
        return 0;
    }
}

อะแดปเตอร์ในหน่วยความจำสำหรับการทดสอบ

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

<?php
final class FixedClock implements Clock {
    public function __construct(private DateTimeImmutable $t) {}
    public function now(): DateTimeImmutable { return $this->t; }
}
final class PlainHasher implements PasswordHasher {
    public function hash(string $p): string { return 'h:' . $p; }
}

$service = new RegisterUserService(
    new InMemoryUsers(),
    new PlainHasher(),
    new FixedClock(new DateTimeImmutable('2026-01-01'))
);
echo 'wired OK', PHP_EOL;

อะแดปเตอร์มีหน้าที่แปล ไม่ใช่ตัดสินใจ

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

  • การแมประหว่างเจสันกับอ็อบเจ็กต์ถ่ายโอนข้อมูล: อะแดปเตอร์
  • การเติมข้อมูลเอนทิตีจากเอสคิวแอล: อะแดปเตอร์
  • "VIP ได้ส่วนลด 10%": แกนหลัก

หนึ่งพอร์ต หลายอะแดปเตอร์

พอร์ตช่วยให้สลับการใช้งานได้ และยังรองรับอะแดปเตอร์แบบ ขนาน ได้ด้วย อะแดปเตอร์อีเมล SMS และ Slack สามารถนำมาประกอบร่วมกันผ่าน NotificationPort ได้ แกนหลักเรียกใช้เมธอดเดียว ส่วนการเชื่อมต่อเป็นผู้กำหนดว่าจะให้ตอบกลับกี่ช่องทาง

<?php
interface Notifier { public function send(string $to, string $msg): void; }

final class CompositeNotifier implements Notifier {
    /** @param Notifier[] $channels */
    public function __construct(private array $channels) {}
    public function send(string $to, string $msg): void {
        foreach ($this->channels as $c) $c->send($to, $msg);
    }
}

$notifier = new CompositeNotifier([new EmailNotifier(), new SmsNotifier()]);
echo 'composed', PHP_EOL;

เฮกซากอนเชื่อมโยงกับโฟลเดอร์อย่างไร

โครงสร้าง PHP ที่ใช้งานได้จริงสำหรับบริบทที่มีขอบเขต:

  • Domain/ — เอนทิตี อ็อบเจ็กต์ค่า และบริการโดเมน
  • Application/Port/In/ — อินเทอร์เฟซของพอร์ตขับเคลื่อน (กรณีใช้งาน)
  • Application/Port/Out/ — อินเทอร์เฟซของพอร์ตที่ถูกขับเคลื่อน (คลังข้อมูล นาฬิกา)
  • Application/ — การนำกรณีใช้งานไปใช้งานจริง
  • Infrastructure/Adapter/In/ — ตัวควบคุม CLI และตัวรับข้อความ
  • Infrastructure/Adapter/Out/ — อะแดปเตอร์ PDO/Doctrine/เอชทีทีพี

จุดประกอบระบบ (การกำหนดค่าคอนเทนเนอร์ DI) จะเชื่อมอะแดปเตอร์ In และ Out เข้ากับพอร์ต

ทดสอบเฮกซากอนทั้งระบบ

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

<?php
// Acceptance test: real use case, fake driven adapters, no I/O
$users = new InMemoryUsers();
$service = new RegisterUserService($users, new PlainHasher(),
    new FixedClock(new DateTimeImmutable('2026-01-01')));

$id = $service->handle('dev@coddykit.com', 'pw');

assert($users->byId($id) !== null);
echo 'acceptance: user persisted via in-memory adapter', PHP_EOL;

ตรวจสอบอย่างรวดเร็ว

ข้อความใดเกี่ยวกับพอร์ตและอะแดปเตอร์ถูกต้อง?

สรุป

แนวทางพอร์ตและอะแดปเตอร์แยกแกนหลักไว้เบื้องหลังอินเทอร์เฟซ:

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

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

บทเรียน “อธิบายพอร์ตและอะแดปเตอร์” ฟรีหรือไม่

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

คุณจะเรียนรู้อะไรในบทเรียน “อธิบายพอร์ตและอะแดปเตอร์”

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

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

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

บทเรียน “อธิบายพอร์ตและอะแดปเตอร์” ใช้เวลานานแค่ไหน

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

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

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

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

  1. จากสถาปัตยกรรมแบบเลเยอร์สู่สถาปัตยกรรมสะอาด
  2. อธิบายพอร์ตและอะแดปเตอร์
  3. กรณีใช้งานและบริการแอปพลิเคชัน
  4. การกลับทิศทางการพึ่งพาในการใช้งานจริง
← กลับไปที่ PHP Academy