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

การกลับทิศทางการพึ่งพาในการใช้งานจริง

เชื่อมอะแดปเตอร์เข้ากับแกนหลักด้วยการกลับทิศทางการพึ่งพา

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

การกลับทิศเทียบกับการฉีด

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

หลักการอย่างแม่นยำ

DIP ระบุว่า:

  • โมดูลระดับสูงไม่ควรพึ่งพาโมดูลระดับต่ำ ทั้งสองควรพึ่งพานามธรรม
  • นามธรรมไม่ควรพึ่งพารายละเอียด รายละเอียดควรพึ่งพานามธรรม

ส่วนที่ละเอียดอ่อนคือ อินเทอร์เฟซต้องเป็นของ ผู้ใช้ ไม่ใช่ผู้สร้างการนำไปใช้ โดเมนของคุณประกาศ PaymentGateway และอะแดปเตอร์ Stripe ต้องทำตามอินเทอร์เฟซนั้น ไม่ใช่ในทางกลับกัน

กำหนดนามธรรมในแกนหลัก

วางอินเทอร์เฟซไว้ถัดจากโค้ดที่ต้องใช้งาน และแสดงออกด้วยคำศัพท์ของโดเมน ไม่ให้ชนิดของ Stripe รั่วไหลเข้ามา

<?php
// src/Billing/Application/Port/PaymentGateway.php
interface PaymentGateway
{
    public function charge(Money $amount, CardToken $token): ChargeId;
}

final class Money {
    public function __construct(public readonly int $cents, public readonly string $currency) {}
}
final class CardToken { public function __construct(public readonly string $value) {} }
final class ChargeId { public function __construct(public readonly string $value) {} }

ทำให้อะแดปเตอร์สอดคล้องกับนามธรรม

อะแดปเตอร์ของโครงสร้างพื้นฐานนำอินเทอร์เฟซของแกนหลักไปใช้งาน และแปลต่อไปยัง SDK ของผู้ให้บริการ ลูกศรการพึ่งพาชี้จากอะแดปเตอร์ (รายละเอียด) ไปยังนามธรรม (นโยบาย) — จึงเกิดการกลับทิศอย่างสมบูรณ์

<?php
final class StripePaymentGateway implements PaymentGateway
{
    public function __construct(private \Stripe\StripeClient $stripe) {}

    public function charge(Money $amount, CardToken $token): ChargeId {
        $intent = $this->stripe->paymentIntents->create([
            'amount'   => $amount->cents,
            'currency' => strtolower($amount->currency),
            'payment_method' => $token->value,
            'confirm'  => true,
        ]);
        return new ChargeId($intent->id);
    }
}

การฉีดผ่านตัวสร้างเป็นค่าเริ่มต้น

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

<?php
final class CheckoutService
{
    public function __construct(
        private PaymentGateway $payments,   // abstraction, not StripeClient
        private OrderRepository $orders,
    ) {}

    public function pay(OrderId $id, CardToken $token): ChargeId {
        $order  = $this->orders->get($id);
        $charge = $this->payments->charge($order->total(), $token);
        $order->markPaid($charge);
        $this->orders->save($order);
        return $charge;
    }
}

จุดประกอบระบบ

การเชื่อมต่อรูปธรรมทั้งหมดต้องเกิดขึ้นในที่เดียวเท่านั้น — จุดประกอบระบบ — ซึ่งควรอยู่ใกล้ main()/จุดเริ่มต้นให้มากที่สุด ส่วนอื่นจะไม่สร้างโครงสร้างพื้นฐานขึ้นมาเอง จุดนี้เป็นเพียงแห่งเดียวที่รู้ว่ามี Stripe อยู่

<?php
// public/index.php — composition root
$stripe   = new \Stripe\StripeClient(getenv('STRIPE_SECRET'));
$gateway  = new StripePaymentGateway($stripe);
$orders   = new PdoOrderRepository(new PDO(getenv('DB_DSN')));
$checkout = new CheckoutService($gateway, $orders);

// Everything below depends only on abstractions
$controller = new CheckoutController($checkout);

เชื่อมต่อด้วยคอนเทนเนอร์ DI

สำหรับแอปพลิเคชันที่ไม่ใช่เรื่องเล็ก คอนเทนเนอร์ (PHP-DI, Symfony) จะช่วยเชื่อมต่อสิ่งต่าง ๆ โดยอัตโนมัติ ขั้นตอนสำคัญคือ ผูกอินเทอร์เฟซเข้ากับการนำไปใช้งาน การเชื่อมโยงอัตโนมัติจะแก้ไขตัวสร้างตามชนิดให้เอง คุณจึงประกาศเพียงแผนผังอินเทอร์เฟซ→การนำไปใช้

<?php
use function DI\autowire;
use function DI\get;

return [
    PaymentGateway::class => autowire(StripePaymentGateway::class),
    OrderRepository::class => autowire(PdoOrderRepository::class),
    \Stripe\StripeClient::class => fn() => new \Stripe\StripeClient(getenv('STRIPE_SECRET')),
    PDO::class => fn() => new PDO(getenv('DB_DSN')),
];

การสลับอะแดปเตอร์พิสูจน์แนวคิด

เนื่องจากแกนหลักพึ่งพาเพียง PaymentGateway การเปลี่ยนผู้ให้บริการหรือการทดสอบแบบออฟไลน์จึงเปลี่ยนเพียงการเชื่อมต่อหนึ่งบรรทัด นี่คือตัวจำลองที่ใช้ในการทดสอบหน่วย — CheckoutService ยังคงเดิมและไม่เคยนำเข้า Stripe

<?php
final class FakeGateway implements PaymentGateway {
    public array $charges = [];
    public function charge(Money $a, CardToken $t): ChargeId {
        $this->charges[] = $a;
        return new ChargeId('ch_test_' . count($this->charges));
    }
}

$fake = new FakeGateway();
$id = $fake->charge(new Money(2500, 'EUR'), new CardToken('tok_visa'));
echo $id->value, ' charges=', count($fake->charges), PHP_EOL; // ch_test_1 charges=1

หลีกเลี่ยงกับดักตัวค้นหาบริการ

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

<?php
// ANTI-PATTERN: hidden dependencies, container leaks everywhere
final class BadCheckout {
    public function __construct(private ContainerInterface $c) {}
    public function pay($id, $token) {
        $gateway = $this->c->get(PaymentGateway::class); // hidden!
        // ...
    }
}

คอนเทนเนอร์อาจปรากฏที่ใด

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

การเชื่อมต่อแบบขี้เกียจโดยไม่ใช้ตัวค้นหาบริการ

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

<?php
final class ReportService
{
    /** @param Closure():PaymentGateway $gatewayFactory */
    public function __construct(private Closure $gatewayFactory) {}

    public function refundIfNeeded(bool $needed): void {
        if (!$needed) return;
        $gateway = ($this->gatewayFactory)(); // built only when required
        // $gateway->charge(...) etc.
    }
}

// Composition root supplies the factory, not the container
$svc = new ReportService(fn() => new StripePaymentGateway($stripe ?? null));
echo 'lazy dependency wired', PHP_EOL;

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

ใครควรเป็นเจ้าของอินเทอร์เฟซ PaymentGateway?

สรุป

คุณเชื่อมอะแดปเตอร์เข้ากับแกนหลักได้อย่างถูกต้อง:

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

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

บทเรียน “การกลับทิศทางการพึ่งพาในการใช้งานจริง” ฟรีหรือไม่

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

คุณจะเรียนรู้อะไรในบทเรียน “การกลับทิศทางการพึ่งพาในการใช้งานจริง”

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

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

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

บทเรียน “การกลับทิศทางการพึ่งพาในการใช้งานจริง” ใช้เวลานานแค่ไหน

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

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

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

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

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