จาก "แค่ทำงานได้" สู่ "โครงสร้างที่ดี": บอกลา Array Hell สู่โลกที่ปลอดภัยด้วย DTO
การใช้ dto แทนการใ้ช้ array
การเปลี่ยนจาก Associative Array มาเป็น DTO (Data Transfer Object) คือก้าวสำคัญของการยกระดับซอร์สโค้ดจากการเขียนเพื่อให้ "โปรแกรมรันผ่าน" ไปสู่การสร้าง ระบบที่ยั่งยืนและดูแลรักษาง่าย (Maintainable Architecture)
1. ทำไมเราถึงควรเลิกใช้ Array ส่งข้อมูล (The "Array Hell")
ในการพัฒนา Web Application ขนาดใหญ่ เรามักต้องส่งข้อมูลข้าม Layer (เช่น จาก Controller ไปยัง Service หรือ Action) ซึ่งการใช้ Array ส่งต่อข้อมูลมักสร้างปัญหาชวนปวดหัว ดังนี้:
- ความไม่แน่นอน (Lack of Structure): เราไม่มีทางรู้เลยว่าใน Array นั้นมี Key อะไรบ้าง จนกว่าจะเข้าไปไล่ดู Code ต้นทาง
- ไม่มี Type Safety: ข้อมูลใน Array เป็นอะไรก็ได้ (String, Int, หรือ Null) เสี่ยงต่อการเกิด Runtime Error
- IDE เดาทางไม่ถูก: Tool อย่าง VS Code หรือ Cursor จะไม่ช่วยทำ Autocomplete ให้เลย เพราะไม่รู้โครงสร้างภายใน
- ยากต่อการ Refactor: หากต้องการเปลี่ยนชื่อ Key เพียงคำเดียว อาจต้องตามแก้กันทั้งโปรเจกต์เพราะค้นหา Reference (ตัวเชื่อมโยง) ได้ยาก
2. DTO คืออะไร?
DTO (Data Transfer Object) คือ Object เรียบง่ายที่ถูกสร้างขึ้นมาเพื่อ "อุ้มข้อมูล" ข้ามกระบวนการ โดยหน้าที่เดียวของมันคือการรวมกลุ่มข้อมูลและกำหนดโครงสร้างที่ชัดเจน (Strict Contract) เพื่อให้ทุก Layer คุยภาษาเดียวกัน
ตารางเปรียบเทียบ: Associative Array vs DTO
คุณสมบัติ | Associative Array | Data Transfer Object (DTO)
โครงสร้างข้อมูล | ยืดหยุ่นเกินไป (Schema-less) | ชัดเจนและแน่นอน (Strict Schema)
การตรวจสอบชนิดข้อมูล | ทำไม่ได้ทันที (ไม่มี Type) | มี Type Hinting ชัดเจน
IDE Support | ไม่มี (ต้องจำ Key เองทั้งหมด) | มี Autocomplete เต็มรูปแบบ
โอกาสเกิดความผิดพลาด | พิมพ์ Key ผิดได้ง่าย (Typo) | ตรวจจับเจอตั้งแต่ตอนเขียน (Static Analysis)
3. เปรียบเทียบด้วย Code (PHP 8+)
DTO (Data Transfer Object) คือ Object เรียบง่ายที่ถูกสร้างขึ้นมาเพื่อ "อุ้มข้อมูล" ข้ามกระบวนการ โดยหน้าที่เดียวของมันคือการรวมกลุ่มข้อมูลและกำหนดโครงสร้างที่ชัดเจน (Strict Contract) เพื่อให้ทุก Layer คุยภาษาเดียวกัน
ตารางเปรียบเทียบ: Associative Array vs DTO
คุณสมบัติ | Associative Array | Data Transfer Object (DTO)
โครงสร้างข้อมูล | ยืดหยุ่นเกินไป (Schema-less) | ชัดเจนและแน่นอน (Strict Schema)
การตรวจสอบชนิดข้อมูล | ทำไม่ได้ทันที (ไม่มี Type) | มี Type Hinting ชัดเจน
IDE Support | ไม่มี (ต้องจำ Key เองทั้งหมด) | มี Autocomplete เต็มรูปแบบ
โอกาสเกิดความผิดพลาด | พิมพ์ Key ผิดได้ง่าย (Typo) | ตรวจจับเจอตั้งแต่ตอนเขียน (Static Analysis)
3. เปรียบเทียบด้วย Code (PHP 8+)
ลองดูความแตกต่างเมื่อเราต้องการจัดการข้อมูลระบบสมัครสมาชิก (User Registration)
- แบบเดิม: การใช้ Array (เสี่ยงและสับสน)
---------------------------------------------------------------------------------------------------
public function store(array $data)
{
// เราไม่รู้เลยว่าต้องเรียก $data['user_name'] หรือ $data['username'] กันแน่?
// และถ้าค่าเป็น null โดยที่เราไม่รู้ ระบบก็อาจจะพังทันที
return User::create([
'name' => $data['name'],
'email' => $data['email'],
]);
}
---------------------------------------------------------------------------------------------------
public function store(array $data)
{
// เราไม่รู้เลยว่าต้องเรียก $data['user_name'] หรือ $data['username'] กันแน่?
// และถ้าค่าเป็น null โดยที่เราไม่รู้ ระบบก็อาจจะพังทันที
return User::create([
'name' => $data['name'],
'email' => $data['email'],
]);
}
---------------------------------------------------------------------------------------------------
- แบบใหม่: การใช้ DTO (ปลอดภัยและเป็นมืออาชีพ)
---------------------------------------------------------------------------------------------------
readonly class UserRegistrationDTO
{
public function __construct(
public string $name,
public string $email,
public ?string $phoneNumber = null, // บอกชัดเจนว่าเป็น Optional (ใส่หรือไม่ใส่ก็ได้)
) {}
// Factory Method สำหรับแปลง Request เป็น DTO ตัวเอง
public static function fromRequest(Request $request): self
{
return new self(
name: $request->validated('name'),
email: $request->validated('email'),
phoneNumber: $request->validated('phone'),
);
}
}
// 🚀 การใช้งานใน Service / Action Layer
public function execute(UserRegistrationDTO $data)
{
// IDE จะรู้ทันทีว่า $data มี property อะไรให้เรียกใช้บ้างผ่าน Autocomplete
return User::create([
'name' => $data->name,
'email' => $data->email,
]);
}
---------------------------------------------------------------------------------------------------
4. ข้อดีของการใช้ DTO ในโปรเจกต์จริง
1) Self-Documenting Code
DTO ทำหน้าที่เป็นเอกสารอธิบายตัวเองในโค้ด เมื่อนักพัฒนาคนอื่นในทีมมาอ่านซอร์สโค้ดต่อ จะรู้ได้ทันทีว่าข้อมูลที่ไหลอยู่ในระบบมีหน้าตาและเงื่อนไขอย่างไร โดยไม่ต้องเดา
2) มั่นใจด้วย Type Safety & Immutability
การใช้ DTO ร่วมกับฟีเจอร์ readonly ใน PHP 8 ช่วยรับประกันว่าข้อมูลจะไม่ถูกแอบแก้ไขระหว่างทาง (Immutability) และได้ชนิดข้อมูลที่ถูกต้องแม่นยำเสมอ
3) ตัวต่อสำคัญใน Action-DTO-Query Pattern
สำหรับระบบที่ออกแบบโครงสร้างสถาปัตยกรรม (Architecture) แขนงนี้ DTO จะทำหน้าที่เป็นสะพานเชื่อมที่ไร้รอยต่อ:
readonly class UserRegistrationDTO
{
public function __construct(
public string $name,
public string $email,
public ?string $phoneNumber = null, // บอกชัดเจนว่าเป็น Optional (ใส่หรือไม่ใส่ก็ได้)
) {}
// Factory Method สำหรับแปลง Request เป็น DTO ตัวเอง
public static function fromRequest(Request $request): self
{
return new self(
name: $request->validated('name'),
email: $request->validated('email'),
phoneNumber: $request->validated('phone'),
);
}
}
// 🚀 การใช้งานใน Service / Action Layer
public function execute(UserRegistrationDTO $data)
{
// IDE จะรู้ทันทีว่า $data มี property อะไรให้เรียกใช้บ้างผ่าน Autocomplete
return User::create([
'name' => $data->name,
'email' => $data->email,
]);
}
---------------------------------------------------------------------------------------------------
4. ข้อดีของการใช้ DTO ในโปรเจกต์จริง
1) Self-Documenting Code
DTO ทำหน้าที่เป็นเอกสารอธิบายตัวเองในโค้ด เมื่อนักพัฒนาคนอื่นในทีมมาอ่านซอร์สโค้ดต่อ จะรู้ได้ทันทีว่าข้อมูลที่ไหลอยู่ในระบบมีหน้าตาและเงื่อนไขอย่างไร โดยไม่ต้องเดา
2) มั่นใจด้วย Type Safety & Immutability
การใช้ DTO ร่วมกับฟีเจอร์ readonly ใน PHP 8 ช่วยรับประกันว่าข้อมูลจะไม่ถูกแอบแก้ไขระหว่างทาง (Immutability) และได้ชนิดข้อมูลที่ถูกต้องแม่นยำเสมอ
3) ตัวต่อสำคัญใน Action-DTO-Query Pattern
สำหรับระบบที่ออกแบบโครงสร้างสถาปัตยกรรม (Architecture) แขนงนี้ DTO จะทำหน้าที่เป็นสะพานเชื่อมที่ไร้รอยต่อ:
- Controller: รับ Request เข้ามา ➡️ ตรวจสอบความถูกต้อง ➡️ แปลงเป็น DTO
- Action/Service: รับ DTO ไปประมวลผล Business Logic ต่ออย่างปลอดภัย
- Query: รับ DTO ไปคัดกรองข้อมูลจาก Database
สรุปเพื่อการทบทวน (Key Takeaways)
- Array เหมาะกับข้อมูลชั่วคราวที่ใช้จบภายใน Function สั้นๆ เท่านั้น
- DTO เหมาะกับการส่งข้อมูลข้าม Class ข้าม Layer หรือใช้เป็นมาตรฐานการสื่อสารในระบบ
- การใช้ DTO ช่วยลดเวลาการ Debug เพราะจะพบ Error ตั้งแต่ขั้นตอนการเขียนโค้ด (เมื่อใช้ร่วมกับ Tool อย่าง PHPStan หรือ Larastan)
- แม้จะรู้สึกว่าต้องเขียนโค้ดเพิ่มขึ้นในตอนแรก (Boilerplate) แต่ในระยะยาว DTO จะช่วยลดหนี้ทางเทคนิค (Technical Debt) ของระบบได้อย่างมหาศาล
laravel