แสดงบทความที่มีป้ายกำกับ Clean Code แสดงบทความทั้งหมด
แสดงบทความที่มีป้ายกำกับ Clean Code แสดงบทความทั้งหมด

วันพฤหัสบดีที่ 27 กุมภาพันธ์ พ.ศ. 2568

[PHP] หลีกเลี่ยงกับดัก! 5 ข้อผิดพลาดที่พบบ่อยในการใช้ if ใน PHP

if เป็นคำสั่งพื้นฐานของ PHP ที่ใช้กันทุกวัน แต่ถึงจะง่าย ก็ยังมีหลายคนพลาด! บางครั้งโค้ดอาจดูเหมือนถูกต้อง แต่กลับสร้างบั๊กมหาศาล หรือทำให้ประสิทธิภาพตกต่ำแบบไม่รู้ตัว วันนี้เรามาดู 5 ข้อผิดพลาดยอดฮิต ในการใช้ if พร้อมวิธีแก้ไขกัน!

1. เปรียบเทียบค่าแบบหลวม (==) แทนที่จะใช้แบบเคร่งครัด (===)

นี่คือหนึ่งในบั๊กที่เจอบ่อยสุด โดยเฉพาะกับคนที่มาจากภาษาอื่นๆ ที่ไม่ได้สนใจเรื่องประเภทของตัวแปร (type) มากนัก

❌ ตัวอย่างโค้ดผิด

$age = '18'; // เป็น string
if ($age == 18) {  
    echo 'ผ่านเกณฑ์';  
} else {  
    echo 'ไม่ผ่าน';  
}

💥 อันนี้จะพิมพ์ "ผ่านเกณฑ์" ทั้งที่ $age เป็น string ('18') และค่าที่เปรียบเทียบเป็น number (18)
👉 เพราะ == จะพยายามแปลงค่าก่อนเปรียบเทียบ เช่น '18' == 18 หรือ '0' == false จะให้ค่าเป็น true
 

✅ วิธีแก้: ใช้ === เพื่อเช็คทั้งค่าและประเภท

if ($age === 18) {  
    echo 'ผ่านเกณฑ์';  
} else {  
    echo 'ไม่ผ่าน';  
}

💡 แบบนี้ 18 (int) กับ '18' (string) จะถือว่า ไม่เท่ากัน และทำให้เงื่อนไขทำงานถูกต้อง
 

ข้อควรจำ:

  • == → เช็คแค่ค่าตรงกัน (อาจเกิดการแปลงค่าโดยอัตโนมัติ)
  • === → เช็คค่าตรงกันและประเภทตรงกัน


2. ลืมปิด if หรือเปิด {} เมื่อเงื่อนไขมีหลายบรรทัด

PHP อนุญาตให้เขียน if แบบไม่มี {} ได้ ถ้ามีแค่บรรทัดเดียว เช่น:

if ($status === 'active')
    echo 'User is active';

✅ อันนี้ไม่มีปัญหา เพราะมีคำสั่งเดียว

แต่ปัญหาใหญ่คือ ถ้าคุณเพิ่มคำสั่งที่สองโดยไม่ได้ใส่ {}:

❌ ตัวอย่างโค้ดผิด

if ($status === 'active')  
    echo 'User is active';  
    echo 'Send email notification'; // 💥 อันนี้จะทำงานเสมอ!

💥 อันนี้จะพิมพ์ "Send email notification" ตลอด เพราะมันไม่ถูกควบคุมโดย if

✅ วิธีแก้: ใช้ {} ให้ชัดเจนเสมอ

if ($status === 'active') {  
    echo 'User is active';  
    echo 'Send email notification';  
}

💡 ใส่ {} ไปเถอะ ป้องกันบั๊กแบบโง่ๆ ได้เยอะ


3. เช็คค่าที่อาจเป็น null หรือ undefined โดยไม่ใช้ isset() หรือ ??

PHP ไม่เหมือนบางภาษา ถ้าคุณพยายามเข้าถึงตัวแปรที่ไม่มีอยู่ มันจะไม่พัง แต่จะแสดง Warning แทน

❌ ตัวอย่างโค้ดผิด

if ($user['name'] === 'John') { // 💥 ถ้าไม่มี 'name' จะเกิด Warning!
    echo 'Hello John!';
}

💥 ถ้าคีย์ 'name' ไม่มีใน $user จะเกิด Warning และอาจทำให้ระบบพัง

✅ วิธีแก้ 1: ใช้ isset()

if (isset($user['name']) && $user['name'] === 'John') {  
    echo 'Hello John!';
}

✅ วิธีแก้ 2: ใช้ Null Coalescing (??) (PHP 7+)

if (($user['name'] ?? '') === 'John') {  
    echo 'Hello John!';
}

💡 ถ้า 'name' ไม่มีอยู่ มันจะใช้ '' แทน ทำให้ไม่เกิด Error


4. ใช้ if-else ยาวเหยียด แทนที่จะใช้ switch, match หรือ array mapping

❌ ตัวอย่างโค้ดผิด

if ($role === 'admin') {  
    $access = 'Full Access';  
} elseif ($role === 'editor') {  
    $access = 'Edit Content';  
} elseif ($role === 'viewer') {  
    $access = 'Read Only';  
} else {  
    $access = 'No Access';  
}

💥 if-else เยอะๆ แบบนี้อ่านยากและขยายยาก

✅ วิธีแก้ 1: ใช้ switch

switch ($role) {  
    case 'admin': $access = 'Full Access'; break;  
    case 'editor': $access = 'Edit Content'; break;  
    case 'viewer': $access = 'Read Only'; break;  
    default: $access = 'No Access';  
}

✅ วิธีแก้ 2: ใช้ match (PHP 8)

$access = match ($role) {  
    'admin' => 'Full Access',  
    'editor' => 'Edit Content',  
    'viewer' => 'Read Only',  
    default => 'No Access',  
};

✅ วิธีแก้ 3: ใช้ Array Mapping

$roleAccess = [  
    'admin' => 'Full Access',  
    'editor' => 'Edit Content',  
    'viewer' => 'Read Only'  
];  

$access = $roleAccess[$role] ?? 'No Access';

💡 Array Mapping เร็วกว่าและขยายง่ายกว่า


5. ใช้ if เช็คหลายเงื่อนไข แต่ลืม Short-Circuit Evaluation

❌ ตัวอย่างโค้ดผิด

if (isset($user) && $user->isActive && $user->hasPermission()) {  
    echo 'Access granted';
}

💥 ปัญหาคือ ถ้า $user เป็น null ระบบจะแครชทันที เพราะมันพยายามเรียก $user->isActive

✅ วิธีแก้: ใช้ && ให้ฉลาด

if ($user && $user->isActive && $user->hasPermission()) {  
    echo 'Access granted';
}

💡 PHP จะตรวจ $user ก่อน ถ้ามันเป็น null จะไม่เช็คเงื่อนไขที่เหลือ ทำให้ไม่เกิด Error



🎯 สรุป: หลีกเลี่ยงกับดัก if ใน PHP

✅ ใช้ === แทน == เพื่อลดปัญหาการเปรียบเทียบแบบหลวม
✅ ใส่ {} เสมอ ป้องกันบั๊กจาก if หลายบรรทัด
✅ เช็คค่าก่อนใช้ isset() หรือ ??
✅ ใช้ switch, match หรือ array แทน if-else ที่ยาวเกินไป
✅ ใช้ Short-Circuit Evaluation ลดโอกาสเกิด Error

ใครที่เคยพลาดข้อไหนบ้าง? 😆 ลองเอาเทคนิคเหล่านี้ไปใช้ แล้วโค้ด PHP ของคุณจะดูโปรขึ้นแน่นอน! 🚀

วันอังคารที่ 25 กุมภาพันธ์ พ.ศ. 2568

[PHP] 5 เทคนิคเพิ่มความฉลาดให้ if statement ใน PHP

ถ้าคุณเป็นสาย PHP แล้วเคยเขียนโค้ดแบบนี้:

if ($age >= 18) {
    $message = 'สามารถเข้าใช้ระบบได้';
} else {
    $message = 'ขออภัย คุณต้องมีอายุ 18 ปีขึ้นไป';
}

ก็ถึงเวลายกระดับสกิลกันแล้ว! 😎

if เป็นหนึ่งในโครงสร้างที่ใช้บ่อยสุดของ PHP แต่การใช้ if อย่างชาญฉลาดจะช่วยให้โค้ดของคุณ อ่านง่ายขึ้น, ทำงานเร็วขึ้น และดูแพงขึ้น 💎 วันนี้เรามาดู 5 เทคนิคที่จะช่วยเพิ่มพลังให้กับ if statement ของคุณ!

1. ใช้ Ternary Operator (?:) ให้โค้ดสั้นลง

ถ้าเงื่อนไขของคุณแค่กำหนดค่าตัวแปร if-else อาจจะดูเทอะทะไปหน่อย ลองใช้ Ternary Operator (?:) เพื่อให้โค้ดสั้นและดูดีขึ้น


🆚 if-else แบบเดิม

if ($age >= 18) {
    $message = 'สามารถเข้าใช้ระบบได้';
} else {
    $message = 'ขออภัย คุณต้องมีอายุ 18 ปีขึ้นไป';
}

✨ ใช้ Ternary Operator

$message = ($age >= 18) ? 'สามารถเข้าใช้ระบบได้' : 'ขออภัย คุณต้องมีอายุ 18 ปีขึ้นไป';


✅ อ่านง่าย ✅ ลดบรรทัดโค้ด ✅ เร็วขึ้น

ข้อควรระวัง: ถ้าเงื่อนไขซับซ้อนมาก อย่าใช้ Ternary Operator เพราะจะทำให้โค้ดอ่านยาก


2. ใช้ Null Coalescing (??) แทนการเช็คค่า null

ถ้าต้องการตรวจสอบว่าตัวแปรมีค่าหรือไม่ แทนที่จะใช้ if ยาวๆ เราสามารถใช้ Null Coalescing Operator (??) ได้

🆚 if-else เช็คค่า null แบบเดิม

if (isset($_GET['user'])) {
    $user = $_GET['user'];
} else {
    $user = 'Guest';
}

✨ ใช้ Null Coalescing Operator

$user = $_GET['user'] ?? 'Guest';

ทำงานยังไง?

  • ถ้า $_GET['user'] มีค่า → ใช้ค่านั้น
  • ถ้าไม่มี (เช่น ไม่ได้ถูกส่งมา หรือเป็น null) → ใช้ค่า 'Guest' แทน


✅ โค้ดสั้นลง ✅ อ่านง่าย ✅ ใช้ในฟอร์มหรือ API ได้ดี


3. ใช้ Early Return แทน Nested If

การซ้อน if (nested if) เยอะๆ ทำให้โค้ดอ่านยากมาก แถมยังต้องไล่ปิด } เยอะสุดๆ 😵

ลองดูโค้ดนี้:

🆚 Nested If แบบเดิม

function checkUser($user) {
    if ($user) {
        if ($user->isActive) {
            return 'Welcome, ' . $user->name;
        } else {
            return 'บัญชีนี้ถูกปิดใช้งาน';
        }
    } else {
        return 'ไม่พบผู้ใช้';
    }
}

ปัญหา: 👎 อ่านยาก, มีการซ้อน if ไม่จำเป็น

✨ ใช้ Early Return ให้ดูโปร

function checkUser($user) {
    if (!$user) return 'ไม่พบผู้ใช้';
    if (!$user->isActive) return 'บัญชีนี้ถูกปิดใช้งาน';
    
    return 'Welcome, ' . $user->name;
}


✅ โค้ดกระชับ ✅ อ่านง่าย ✅ ลดการซ้อนของ if


4. ใช้ switch หรือ match แทน if-else ที่ยาวเกินไป

ถ้า if-else มีหลายเงื่อนไขแบบนี้:

🆚 if-else ยาวๆ

if ($status === 'pending') {
    $message = 'รอดำเนินการ';
} elseif ($status === 'approved') {
    $message = 'อนุมัติแล้ว';
} elseif ($status === 'rejected') {
    $message = 'ถูกปฏิเสธ';
} else {
    $message = 'สถานะไม่ถูกต้อง';
}

✨ ใช้ switch แทน

switch ($status) {
    case 'pending':
        $message = 'รอดำเนินการ';
        break;
    case 'approved':
        $message = 'อนุมัติแล้ว';
        break;
    case 'rejected':
        $message = 'ถูกปฏิเสธ';
        break;
    default:
        $message = 'สถานะไม่ถูกต้อง';
}

✨ ใช้ match (PHP 8)

$message = match ($status) {
    'pending' => 'รอดำเนินการ',
    'approved' => 'อนุมัติแล้ว',
    'rejected' => 'ถูกปฏิเสธ',
    default => 'สถานะไม่ถูกต้อง',
};

✅ switch/match อ่านง่ายกว่า ✅ เหมาะกับหลายเงื่อนไข


5. ใช้ Array Mapping แทน If-Else

ถ้า if-else แค่แมปค่า เราสามารถใช้ Array Mapping แทนได้

🆚 if-else แบบเดิม


if ($role === 'admin') {
    $permissions = ['edit', 'delete', 'view'];
} elseif ($role === 'editor') {
    $permissions = ['edit', 'view'];
} else {
    $permissions = ['view'];
}

✨ ใช้ Array Mapping


$rolePermissions = [
    'admin' => ['edit', 'delete', 'view'],
    'editor' => ['edit', 'view'],
    'user' => ['view']
];

$permissions = $rolePermissions[$role] ?? ['view'];


✅ ลดโค้ด ✅ อ่านง่าย ✅ ขยายง่าย


🎯 สรุป: เทคนิคเพิ่มความฉลาดให้ if statement

✅ Ternary Operator (?:) → ลดโค้ด if-else ที่กำหนดค่า
✅ Null Coalescing (??) → เช็คค่า null แบบง่ายๆ
✅ Early Return → ลด nested if เพื่อให้โค้ดดูสะอาด
✅ ใช้ switch หรือ match → แทน if-else ที่ยาวเกินไป
✅ Array Mapping → ลดการใช้ if-else สำหรับการแมปค่า

ลองนำเทคนิคเหล่านี้ไปใช้ แล้วโค้ด PHP ของคุณจะดูโปรขึ้นอีกระดับ! 🚀

วันอาทิตย์ที่ 23 กุมภาพันธ์ พ.ศ. 2568

[PHP] Magic of Match: ใช้ Match Expression ใน PHP 8 แทน if-else แบบเดิม

ถ้าพูดถึงการเขียนเงื่อนไขใน PHP เชื่อว่าหลายคนต้องนึกถึง if-else เป็นอันดับแรก แต่พอเงื่อนไขเยอะขึ้น โค้ดก็เริ่มรก ยาวเป็นหางว่าว และอ่านยากสุดๆ

แต่เดี๋ยวก่อน! PHP 8 มาพร้อมกับพระเอกตัวใหม่ที่ชื่อว่า Match Expression ที่จะมาช่วยให้โค้ดเงื่อนไขของคุณสั้น กระชับ และดูโปรกว่าเดิมหลายเท่า

ในบทความนี้ เราจะมาทำความรู้จักกับ match ว่ามันคืออะไร ใช้ยังไง และทำไมมันถึงเป็นตัวตายตัวแทนของ if-else หรือ switch-case ได้!

🎩 Match Expression คืออะไร?

match เป็น expression ใหม่ใน PHP 8 ที่คล้าย switch แต่มีความสามารถเหนือกว่า เพราะมัน:
✅ สั้นกว่า – เขียนได้กระชับ ไม่ต้องมี break;
✅ เทียบค่าแบบ strict (===) – ไม่ต้องกลัวเรื่อง type conversion แปลกๆ
✅ คืนค่าได้เลย – ใช้ return ค่าได้โดยตรง

ลองดูตัวอย่างนี้ 👇

🆚 if-else แบบเดิม


$grade = 'B';
 
if ($grade === 'A') {
    $message = 'เยี่ยมไปเลย! 🎉';
} elseif ($grade === 'B') {
    $message = 'ดีมาก! 👍';
} elseif ($grade === 'C') {
    $message = 'พอไหว 😅';
} else {
    $message = 'ต้องพยายามอีกนิด 💪';
}
 
echo $message;

โค้ดนี้อ่านง่ายตอนเงื่อนไขน้อยๆ แต่ถ้ามีหลายระดับ ความซับซ้อนจะพุ่งปรี๊ด

✨ ใช้ match ให้สวยและแพงกว่าเดิม


$grade = 'B';

$message = match ($grade) {
    'A' => 'เยี่ยมไปเลย! 🎉',
    'B' => 'ดีมาก! 👍',
    'C' => 'พอไหว 😅',
    default => 'ต้องพยายามอีกนิด 💪',
};

echo $message;


เห็นไหม? match ทำให้โค้ดสั้นลง อ่านง่ายขึ้น ไม่ต้องพึ่ง break; หรือ return หลายบรรทัด


🔥 ข้อดีของ Match ที่เหนือกว่า If-Else และ Switch

1. Strict Comparison (===) ในตัว

match ใช้การเปรียบเทียบแบบ strict (===) ต่างจาก switch ที่ใช้ loose comparison (==)
 

🛑 switch มีปัญหาอะไร?

$value = '1';

switch ($value) {
    case 1:
        echo 'Yes';
        break;
    default:
        echo 'No';
}

💥 อันนี้จะแสดง Yes เพราะ switch เปรียบเทียบแบบ loose (==) ทำให้ '1' เท่ากับ 1


✅ match แก้ปัญหานี้ได้


$value = '1';

echo match ($value) {
    1 => 'Yes',
    default => 'No',
};


✨ อันนี้จะออก No เพราะ match ใช้ === ทำให้ '1' กับ 1 ไม่เท่ากัน

2. คืนค่าได้ทันที (Expression-based)

match สามารถ return ค่าออกมาได้เลย ไม่ต้องสร้างตัวแปรก่อน เช่น

function getDiscount($membership) {
    return match ($membership) {
        'gold' => 20,
        'silver' => 10,
        'bronze' => 5,
        default => 0,
    };
}

echo getDiscount('gold'); // 20


แต่ถ้าเป็น switch หรือ if-else ต้องทำแบบนี้:


function getDiscount($membership) {
    switch ($membership) {
        case 'gold':
            return 20;
        case 'silver':
            return 10;
        case 'bronze':
            return 5;
        default:
            return 0;
    }
}

👆 เยอะกว่ากันเห็นๆ


3. ใช้หลายเงื่อนไขในบรรทัดเดียว

อยากให้หลายๆ ค่ามีผลลัพธ์เหมือนกัน? ทำได้ง่ายๆ

$category = 'mango';

$type = match ($category) {
    'apple', 'mango', 'banana' => 'fruit',
    'carrot', 'spinach' => 'vegetable',
    default => 'unknown',
};

echo $type; // fruit

💡 match รองรับการแมปหลายค่าให้เป็นค่าเดียว

4. ไม่มีปัญหา "ตกเคส" แบบ Switch

switch ถ้าเราลืมใส่ break; อาจเกิดปัญหาการตกเคส เช่น

$color = 'red';

switch ($color) {
    case 'red':
        echo 'Stop!';
    case 'yellow':
        echo 'Slow down!';
    case 'green':
        echo 'Go!';
}

💥 อันนี้จะแสดง Stop!Slow down!Go! เพราะ switch ไม่ได้หยุดที่ case red

แต่ match ไม่มีปัญหานี้ เพราะมันไม่ทำงานแบบ fallthrough

$color = 'red';

echo match ($color) {
    'red' => 'Stop!',
    'yellow' => 'Slow down!',
    'green' => 'Go!',
};

✅ แสดงแค่ Stop! ถูกต้องเป๊ะ!


🤔 เมื่อไหร่ควรใช้ Match?

    ✅ เมื่อมีการแมปค่าตรงๆ (mapping values)
    ✅ เมื่ออยากให้โค้ดสั้น กระชับ อ่านง่าย
    ✅ เมื่ออยากได้ strict comparison (===)

แต่! match ไม่สามารถใช้เงื่อนไขซับซ้อนแบบ if-else ได้ เช่น

$age = 20;

$category = match (true) { // ❌ ไม่เวิร์คแบบนี้
    $age < 18 => 'เด็ก',
    $age >= 18 && $age < 60 => 'ผู้ใหญ่',
    default => 'ผู้สูงอายุ',
};

💥 แบบนี้ match จะ error เพราะมันไม่ได้ออกแบบมาให้ใช้เงื่อนไขที่ซับซ้อนแบบนี้



🎯 สรุป: Match ดีกว่า If-Else และ Switch ยังไง?

✅ โค้ดสั้น กระชับ อ่านง่าย
✅ ใช้ === ป้องกันปัญหาประเภทข้อมูล
✅ คืนค่าได้เลย (expression-based)
✅ ไม่มีปัญหาตกเคสแบบ switch
✅ รองรับหลายค่าในเงื่อนไขเดียว

แต่ก็มีข้อจำกัดตรงที่ไม่รองรับเงื่อนไขที่ซับซ้อนเกินไป

ใครที่ยังใช้ if-else หรือ switch กันอยู่ ลองปรับมาใช้ match ดู รับรองว่าโค้ดดูแพงขึ้นแน่นอน! 🚀

วันเสาร์ที่ 22 กุมภาพันธ์ พ.ศ. 2568

[PHP] SOLID Principles เขียนโค้ดยังไงให้ Maintain ง่าย

SOLID Principles คืออะไร?

ถ้าคุณเป็น PHP Developer ที่ต้องการเขียนโค้ดให้ดีขึ้น มีโครงสร้างที่ชัดเจน และง่ายต่อการดูแล (maintain) SOLID Principles คือแนวคิดที่คุณต้องรู้!

SOLID เป็นชุดของ 5 หลักการออกแบบเชิงวัตถุ (OOP) ที่ช่วยให้โค้ดของคุณ อ่านง่าย, ขยายได้, และลดบั๊กในอนาคต โดยคำว่า SOLID มาจากอักษรตัวแรกของ 5 หลักการดังนี้:

  • S – Single Responsibility Principle (SRP)
  • O – Open/Closed Principle (OCP)
  • L – Liskov Substitution Principle (LSP)
  • I – Interface Segregation Principle (ISP)
  • D – Dependency Inversion Principle (DIP)


ในบทความนี้ เราจะมาดูกันว่าแต่ละหลักการทำงานยังไง และเราจะใช้มันกับ PHP ได้ยังไง


1. Single Responsibility Principle (SRP) - หนึ่งคลาส หนึ่งหน้าที่

หลักการ:

    "A class should have only one reason to change."
    (คลาสควรมีเหตุผลเดียวที่ต้องเปลี่ยนแปลง)

แปลว่า: คลาสหนึ่งควรมีหน้าที่เดียว ไม่ควรทำหลายอย่างในที่เดียวกัน

❌ ตัวอย่างที่ไม่ดี:

class User {
    public function saveToDatabase() {
        // บันทึกข้อมูลลง Database
    }

    public function sendEmail() {
        // ส่งอีเมลแจ้งเตือน
    }
}

User ควรรับผิดชอบเฉพาะข้อมูลของผู้ใช้ ไม่ควรจัดการเรื่อง Database หรือ Email

✅ วิธีแก้:

class UserRepository {
    public function save(User $user) {
        // บันทึกข้อมูลลง Database
    }
}

class EmailService {
    public function send(User $user) {
        // ส่งอีเมลแจ้งเตือน
    }
}

ข้อดีของ SRP:

  • แยกโค้ดตามหน้าที่ ทำให้แก้ไขง่าย
  • ลดผลกระทบเมื่อเปลี่ยนแปลงโค้ด



2. Open/Closed Principle (OCP) - เปิดสำหรับขยาย ปิดสำหรับแก้ไข

หลักการ:

    "Software entities should be open for extension but closed for modification."
    (โค้ดควรเปิดให้ขยาย แต่ปิดสำหรับการแก้ไข)

แปลว่า: เราควรออกแบบโค้ดให้เพิ่มฟีเจอร์ใหม่ได้โดยไม่ต้องแก้ไขโค้ดเก่า

❌ ตัวอย่างที่ไม่ดี:

class Payment {
    public function pay($method) {
        if ($method == "credit_card") {
            // จ่ายผ่านบัตรเครดิต
        } elseif ($method == "paypal") {
            // จ่ายผ่าน PayPal
        }
    }
}

ถ้าอยากเพิ่มวิธีชำระเงินใหม่ เราต้องไปแก้ไขโค้ดเดิม ซึ่งไม่ดีเลย

✅ วิธีแก้: ใช้ Polymorphism

interface PaymentMethod {
    public function pay();
}

class CreditCardPayment implements PaymentMethod {
    public function pay() {
        // จ่ายผ่านบัตรเครดิต
    }
}

class PayPalPayment implements PaymentMethod {
    public function pay() {
        // จ่ายผ่าน PayPal
    }
}

class PaymentProcessor {
    public function process(PaymentMethod $paymentMethod) {
        $paymentMethod->pay();
    }
}


ข้อดีของ OCP:

  • เพิ่มวิธีชำระเงินใหม่ได้ง่าย โดยไม่ต้องแก้โค้ดเดิม
  • ลดโอกาสเกิดบั๊กจากการแก้ไขโค้ด


3. Liskov Substitution Principle (LSP) - ใช้แทนกันได้โดยไม่พัง

หลักการ:

    "Objects of a derived class must be substitutable for objects of the base class."
    (คลาสลูกควรใช้แทนคลาสแม่ได้โดยไม่ทำให้โปรแกรมพัง)

❌ ตัวอย่างที่ไม่ดี:

class Bird {
    public function fly() {
        return "บินไปเลย!";
    }
}

class Penguin extends Bird {
    public function fly() {
        throw new Exception("เพนกวินบินไม่ได้!");
    }
}

Penguin เป็น Bird แต่ดันบินไม่ได้ ทำให้โปรแกรมอาจพังเมื่อเรียก fly()

✅ วิธีแก้: แยก Bird ออกเป็น FlyingBird และ NonFlyingBird

abstract class Bird {}

class FlyingBird extends Bird {
    public function fly() {
        return "บินไปเลย!";
    }
}

class Penguin extends Bird {
    public function swim() {
        return "ว่ายน้ำ!";
    }
}


ข้อดีของ LSP:

  • ใช้คลาสลูกแทนคลาสแม่ได้อย่างปลอดภัย
  • ลดปัญหาบั๊กที่เกิดจากพฤติกรรมที่ไม่คาดคิด


4. Interface Segregation Principle (ISP) - ใช้ Interface ให้เหมาะสม

หลักการ:

    "Clients should not be forced to depend on methods they do not use."
    (อย่าทำให้คลาสต้องพึ่งพาเมธอดที่มันไม่ได้ใช้)

❌ ตัวอย่างที่ไม่ดี:

interface Worker {
    public function work();
    public function eat();
}

class Robot implements Worker {
    public function work() {
        return "ทำงาน!";
    }

    public function eat() {
        throw new Exception("หุ่นยนต์ไม่กินข้าว!");
    }
}

✅ วิธีแก้: แยก Interface

interface Workable {
    public function work();
}

interface Eatable {
    public function eat();
}

class Robot implements Workable {
    public function work() {
        return "ทำงาน!";
    }
}

ข้อดีของ ISP:

  • ลดการใช้ method ที่ไม่จำเป็น
  • คลาสลูก implement interface เท่าที่จำเป็น


5. Dependency Inversion Principle (DIP) - แยกส่วนการพึ่งพาออกจากกัน

หลักการ:

    "Depend on abstractions, not concretions."
    (ควรพึ่งพา abstraction มากกว่าการอ้างอิงถึง class โดยตรง)

❌ ตัวอย่างที่ไม่ดี:

class MySQLDatabase {
    public function connect() {
        return "เชื่อมต่อ MySQL!";
    }
}

class UserRepository {
    private $db;

    public function __construct() {
        $this->db = new MySQLDatabase(); // ❌ ผูกกับ MySQL โดยตรง
    }
}

✅ วิธีแก้: ใช้ Dependency Injection

interface Database {
    public function connect();
}

class MySQLDatabase implements Database {
    public function connect() {
        return "เชื่อมต่อ MySQL!";
    }
}

class UserRepository {
    private $db;

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

ข้อดีของ DIP:

  • เปลี่ยน Database ได้ง่าย
  • ลดการพึ่งพาของ class




SOLID Principles ช่วยให้โค้ด PHP ของเรามีโครงสร้างที่ดีขึ้น แก้ไขและขยายโค้ดได้ง่าย ลดบั๊กที่อาจเกิดขึ้น ถ้าคุณต้องการเป็น PHP Developer ที่เก่งขึ้น SOLID เป็นแนวคิดที่คุณควรรู้!

วันพฤหัสบดีที่ 20 กุมภาพันธ์ พ.ศ. 2568

[PHP] Dependency Injection หลักการออกแบบที่ช่วยให้โค้ดสะอาดขึ้น

Dependency Injection คืออะไร?

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

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

พูดง่ายๆ DI คือการ ส่ง dependencies เข้าไปใน object แทนที่ object จะสร้างมันขึ้นมาเอง วิธีนี้ช่วยให้โค้ดของเรายืดหยุ่น และเปลี่ยนแปลงได้ง่ายขึ้น

 

ทำไมต้องใช้ Dependency Injection?

1. โค้ดสะอาดขึ้น (Clean Code)

ถ้า class ต้องสร้าง dependencies เอง มันจะทำให้โค้ดของเราดูรกไปหมด DI ช่วยให้เราสามารถแยกความรับผิดชอบของแต่ละ class ออกจากกันได้อย่างชัดเจน

2. ทดสอบง่ายขึ้น (Easier Testing)

ถ้าเราใช้ DI เราสามารถ Inject mock dependencies เข้าไปตอนเขียน unit test ได้เลย ไม่ต้องไปยุ่งกับ database หรือ external services ทำให้การทดสอบรวดเร็วและแม่นยำขึ้น

3. ลดการผูกมัดระหว่าง class (Loose Coupling)

สมมติว่าเรามี class ที่ต้องใช้ Logger ถ้า Logger ถูกสร้างขึ้นภายใน class นั้นๆ เวลาเราอยากเปลี่ยน Logger ใหม่ เราต้องไปแก้โค้ดใน class นั้นด้วย ซึ่งไม่ดีเลย แต่ถ้าใช้ DI เราสามารถเปลี่ยน Logger ใหม่ได้โดยไม่ต้องแตะโค้ดเดิม

4. รองรับการเปลี่ยนแปลงและขยายโค้ดได้ง่าย (More Flexibility)

เมื่อโค้ดของเราไม่ต้องพึ่ง dependencies แบบตายตัว การเปลี่ยนแปลงก็จะง่ายขึ้น เช่น ถ้าเราต้องการเปลี่ยนจาก FileLogger เป็น DatabaseLogger เราสามารถทำได้โดยไม่ต้องไปไล่แก้โค้ดทุกที่
ตัวอย่างการใช้ Dependency Injection ใน PHP

ลองดูตัวอย่างง่ายๆ

 

โค้ดแบบไม่มี DI (Tightly Coupled Code)

class Logger {
    public function log($message) {
        echo "Log: " . $message;
    }
}

class UserService {
    private $logger;

    public function __construct() {
        $this->logger = new Logger(); // สร้าง instance เอง = tightly coupled
    }

    public function registerUser($username) {
        $this->logger->log("User $username registered.");
    }
}

ปัญหา:

  • UserService สร้าง Logger เอง ทำให้เราต้องแก้โค้ดทุกครั้งถ้าอยากเปลี่ยนไปใช้ Logger ตัวอื่น
  • ทำให้เขียน unit test ยาก เพราะ Logger ถูกสร้างขึ้นภายใน class

 

โค้ดที่ใช้ Dependency Injection (Loosely Coupled Code)


class Logger {
    public function log($message) {
        echo "Log: " . $message;
    }
}

class UserService {
    private $logger;

    public function __construct(Logger $logger) { // Inject ผ่าน constructor
        $this->logger = $logger;
    }

    public function registerUser($username) {
        $this->logger->log("User $username registered.");
    }
}

// ใช้งาน
$logger = new Logger();
$userService = new UserService($logger);
$userService->registerUser("JohnDoe");

ข้อดีของวิธีนี้คือ

  • UserService ไม่ต้องสร้าง Logger เอง แค่รับมันเข้ามาใช้
  • ถ้าเราอยากเปลี่ยนไปใช้ Logger ตัวใหม่ ก็แค่ Inject Logger ตัวใหม่เข้าไป
  • ง่ายต่อการเขียน unit test


รูปแบบของ Dependency Injection ใน PHP

1. Constructor Injection (นิยมใช้มากที่สุด)

แบบที่เราเห็นในตัวอย่างด้านบน นี่เป็นวิธีที่ดีที่สุด เพราะทำให้ object ถูกสร้างขึ้นมาพร้อม dependencies ที่มันต้องการ

class UserService {
    private $logger;

    public function __construct(Logger $logger) {
        $this->logger = $logger;
    }
}
 

 

2. Setter Injection (กำหนด dependencies ผ่าน method)

class UserService {
    private $logger;

    public function setLogger(Logger $logger) {
        $this->logger = $logger;
    }
}

ข้อเสียคือเราอาจลืมเรียก setLogger() ทำให้ object อยู่ในสถานะที่ไม่สมบูรณ์

 

3. Interface Injection (ใช้ Interface กำหนด dependencies)

interface LoggerInterface {
    public function log($message);
}

class FileLogger implements LoggerInterface {
    public function log($message) {
        file_put_contents('log.txt', $message, FILE_APPEND);
    }
}

class UserService {
    private $logger;

    public function __construct(LoggerInterface $logger) {
        $this->logger = $logger;
    }
}

ข้อดีของวิธีนี้คือเราสามารถเปลี่ยน Logger ได้ง่ายมาก


ใช้ Dependency Injection Container (DIC) เพื่อจัดการ dependencies

ถ้าโปรเจกต์ของเรามีหลาย dependencies การ Inject dependencies ด้วยตัวเองอาจจะวุ่นวาย เราสามารถใช้ Dependency Injection Container (DIC) มาช่วยได้ เช่น

  • Laravel → มี Service Container ในตัว
  • Symfony → ใช้ Symfony Dependency Injection Component
  • PHP-DI → Lightweight DI Container


ตัวอย่างการใช้ PHP-DI

use DI\Container;

$container = new Container();
$container->set(Logger::class, new Logger());

$userService = new UserService($container->get(Logger::class));


สรุป

Dependency Injection เป็นเทคนิคที่ช่วยให้โค้ดของเราสะอาดขึ้น ยืดหยุ่นขึ้น และง่ายต่อการทดสอบ การใช้ DI สามารถช่วยให้โค้ดของเราเป็นแบบ loosely coupled ทำให้สามารถเปลี่ยนแปลง dependencies ได้โดยไม่ต้องแก้โค้ดเดิมมากมาย

ถ้าคุณยังไม่ได้ใช้ DI ใน PHP แนะนำให้เริ่มต้นวันนี้ แล้วคุณจะเห็นความแตกต่าง!