跳到主要内容

เนื้อหาภาษาไทย / 泰语内容

系统指南 内容已审核

การใช้ Dependency Injection (DI) ใน PHP

บทความนี้อธิบายหลักการ Dependency Injection (DI) ใน PHP ซึ่งเป็นเทคนิคที่ช่วยให้โค้ดมีความยืดหยุ่น, ทดสอบได้ง่าย และนำกลับมาใช้ใหม่ได้ โดย DI คือการส่ง Dependen...

การใช้ Dependency Injection (DI) ใน PHP

ในบทที่แล้ว เราได้เรียนรู้เกี่ยวกับการเขียน Unit Tests ด้วย PHPUnit เพื่อให้มั่นใจในคุณภาพและความถูกต้องของโค้ดของเรา ในบทนี้ เราจะมาเรียนรู้เกี่ยวกับ Dependency Injection (DI) ซึ่งเป็นหลักการออกแบบที่ช่วยให้โค้ดของเรามีความยืดหยุ่น (flexible), ทดสอบได้ง่าย (testable), และนำกลับมาใช้ใหม่ได้ (reusable) มากยิ่งขึ้น

Dependency คืออะไร?

ก่อนที่เราจะพูดถึง Dependency Injection เรามาทำความเข้าใจก่อนว่า "Dependency" คืออะไร ในบริบทของการเขียนโปรแกรม Dependency หมายถึง เมื่อคลาสหนึ่ง (Class A) ต้องการใช้ฟังก์ชันการทำงานของอีกคลาสหนึ่ง (Class B) Class A จะมี Dependency ต่อ Class B

ลองพิจารณาตัวอย่างง่ายๆ:

class UserProfile {
    private $database;

    public function __construct() {
        $this->database = new Database();
    }

    public function getUserProfile($userId) {
        // ดึงข้อมูลผู้ใช้จากฐานข้อมูลโดยใช้ $this->database
        return $this->database->query("SELECT * FROM users WHERE id = " . $userId);
    }
}

ในตัวอย่างนี้ คลาส UserProfile มี Dependency ต่อคลาส Database เพราะ UserProfile ต้องใช้ Database เพื่อดึงข้อมูลผู้ใช้

ปัญหาของการสร้าง Dependency โดยตรง (Hard Dependency)

การสร้าง Dependency โดยตรงภายในคลาส (เช่น $this->database = new Database();) ก่อให้เกิดปัญหาหลายอย่าง:

  • ความยืดหยุ่นต่ำ: หากเราต้องการเปลี่ยนไปใช้ฐานข้อมูลอื่น (เช่น PostgreSQL) เราจะต้องแก้ไขโค้ดภายในคลาส UserProfile
  • ทดสอบได้ยาก: ในระหว่างการทดสอบ (Unit Testing) เราไม่สามารถจำลอง (mock) คลาส Database ได้ง่าย ทำให้การทดสอบ UserProfile กลายเป็น Integration Test มากกว่า Unit Test
  • นำกลับมาใช้ใหม่ได้ยาก: คลาส UserProfile ผูกติดอยู่กับคลาส Database ทำให้ยากต่อการนำ UserProfile ไปใช้ในบริบทอื่นที่ไม่ต้องการใช้ Database

Dependency Injection (DI) คืออะไร?

Dependency Injection (DI) เป็นหลักการออกแบบที่แก้ปัญหา Hard Dependency โดยการ "Inject" หรือ "ส่งมอบ" Dependency ให้กับคลาส แทนที่จะให้คลาสสร้าง Dependency เอง DI ช่วยให้เราสามารถเปลี่ยน Dependency ได้ง่าย, ทดสอบโค้ดได้ง่ายขึ้น, และทำให้โค้ดมีความยืดหยุ่นมากขึ้น

โดยพื้นฐานแล้ว DI คือการ "ส่ง" (inject) สิ่งที่คลาสต้องการ (dependency) เข้าไปในคลาส แทนที่คลาสจะต้องสร้างมันขึ้นมาเอง

รูปแบบการทำ Dependency Injection

มีหลายรูปแบบในการทำ Dependency Injection แต่รูปแบบที่พบบ่อยที่สุดคือ Constructor Injection

Constructor Injection

Constructor Injection คือการ Inject Dependency ผ่าน Constructor ของคลาส:

class UserProfile {
    private $database;

    public function __construct(Database $database) {
        $this->database = $database;
    }

    public function getUserProfile($userId) {
        // ดึงข้อมูลผู้ใช้จากฐานข้อมูลโดยใช้ $this->database
        return $this->database->query("SELECT * FROM users WHERE id = " . $userId);
    }
}

// การใช้งาน
$database = new Database();
$userProfile = new UserProfile($database); // Inject Database object
$userData = $userProfile->getUserProfile(123);

ในตัวอย่างนี้ เรา Inject Database object เข้าไปใน UserProfile ผ่าน Constructor แทนที่จะให้ UserProfile สร้าง Database object เอง

ข้อดีของ Constructor Injection:

  • Dependency ถูกกำหนดไว้อย่างชัดเจนใน Constructor ทำให้ง่ายต่อการเข้าใจว่าคลาสต้องการอะไร
  • รับประกันว่า Dependency จะถูก Inject เมื่อสร้าง Object
  • ทำให้การทดสอบง่ายขึ้น เพราะเราสามารถ Inject Mock object เข้าไปได้

ตัวอย่างการทดสอบด้วย Mock object:

// สมมติว่าเรามี MockDatabase class สำหรับการทดสอบ
class MockDatabase {
    public function query($sql) {
        // คืนค่าข้อมูลจำลองสำหรับการทดสอบ
        return ['id' => 123, 'name' => 'Test User'];
    }
}

// ใน Unit Test
$mockDatabase = new MockDatabase();
$userProfile = new UserProfile($mockDatabase); // Inject MockDatabase
$userData = $userProfile->getUserProfile(123);

// ตรวจสอบว่า $userData เป็นข้อมูลจำลองที่เรากำหนดไว้
// ...

Service Container (Dependency Injection Container)

เมื่อโปรเจกต์ของเรามีขนาดใหญ่ขึ้นและมี Dependency ซับซ้อนมากขึ้น การจัดการ Dependency ด้วยตนเอง (เช่น การสร้าง Object และ Inject ทีละตัว) จะกลายเป็นเรื่องยุ่งยาก Service Container หรือ Dependency Injection Container (DIC) คือเครื่องมือที่ช่วยเราจัดการ Dependency เหล่านี้

Service Container มีหน้าที่หลักในการ:

  • ลงทะเบียน (Register) Dependency: บอก Container ว่าจะสร้าง Object ของแต่ละคลาสอย่างไร
  • แก้ไข (Resolve) Dependency: เมื่อเราต้องการ Object ของคลาสใด Container จะสร้าง Object และ Inject Dependency ที่จำเป็นให้โดยอัตโนมัติ

ตัวอย่างการใช้ Service Container (อย่างง่าย):

class Container {
    private $bindings = [];

    public function bind($abstract, $concrete) {
        $this->bindings[$abstract] = $concrete;
    }

    public function resolve($abstract) {
        if (isset($this->bindings[$abstract])) {
            $concrete = $this->bindings[$abstract];

            if (is_callable($concrete)) {
                return $concrete($this); // Resolve using a closure
            }

            return new $concrete; // Resolve using class name
        }

        return null; // Or throw an exception if not found
    }
}

// การใช้งาน
$container = new Container();

// ลงทะเบียน Database class
$container->bind('Database', 'Database');

// ลงทะเบียน UserProfile class
$container->bind('UserProfile', function ($container) {
    return new UserProfile($container->resolve('Database'));
});

// Resolve UserProfile class
$userProfile = $container->resolve('UserProfile');
$userData = $userProfile->getUserProfile(123);

ในตัวอย่างนี้ เราสร้าง Container class อย่างง่ายขึ้นมาเพื่อแสดงแนวคิด Service Container จริงๆ แล้ว Framework ส่วนใหญ่ (เช่น Laravel, Symfony) จะมี Service Container มาให้พร้อมใช้งานอยู่แล้ว

ข้อดีของการใช้ Service Container:

  • ลดความซับซ้อนในการจัดการ Dependency
  • ช่วยให้โค้ดมีความเป็นระเบียบและง่ายต่อการบำรุงรักษา
  • รองรับการทำ Dependency Injection ได้อย่างเต็มรูปแบบ

สรุป

Dependency Injection (DI) เป็นหลักการออกแบบที่สำคัญที่ช่วยให้โค้ดของเรามีความยืดหยุ่น, ทดสอบได้ง่าย, และนำกลับมาใช้ใหม่ได้ การใช้ Constructor Injection เป็นรูปแบบที่พบบ่อยที่สุดในการทำ DI และ Service Container ช่วยให้เราจัดการ Dependency ที่ซับซ้อนได้อย่างมีประสิทธิภาพ

ในบทถัดไป เราจะมาเรียนรู้เกี่ยวกับการสร้าง Middleware ใน PHP ซึ่งเป็นอีกหนึ่งแนวคิดที่สำคัญในการสร้าง Application ที่มีความยืดหยุ่นและจัดการ Request/Response ได้อย่างมีประสิทธิภาพ

Office.in.th Custom Service

想基于此结构开发或定制系统?

Office.in.th 承接企业级 Web Application、API Integration 以及政府/企业管理系统的设计与开发,并提供长期支持。

查看软件开发服务