การกลับทิศทางการพึ่งพาในการใช้งานจริง
เชื่อมอะแดปเตอร์เข้ากับแกนหลักด้วยการกลับทิศทางการพึ่งพา
การกลับทิศทางการพึ่งพาในการใช้งานจริง เป็นบทเรียน 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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- จากสถาปัตยกรรมแบบเลเยอร์สู่สถาปัตยกรรมสะอาด
- อธิบายพอร์ตและอะแดปเตอร์
- กรณีใช้งานและบริการแอปพลิเคชัน
- การกลับทิศทางการพึ่งพาในการใช้งานจริง