nattapong99 / book

0 stars 0 forks source link

Symfony Databases and the Doctrine ORM #1

Open nattapong99 opened 6 years ago

nattapong99 commented 6 years ago

Databases and the Doctrine ORM

หนึ่งในสั่งที่พบมากที่สุดและเป็นงานท้าทายสำหรับแอพพลิเคชั่นใด ๆ ก็ตามจะเกี่ยวข้องกับการ persisting และอ่านข้อมูลจากฐานข้อมูล แม้ว่า Symfony Framework จะไม่ได้รวม component ที่ใช้ทำงานกับฐานข้อมูล แต่ก็มีการใช้ third-party library เรียกว่า Doctrine โดยเป้าหมายอย่างเดียวของ Doctrine คือการช่วยให้คุณมี เครื่องมือที่มีประสิทธิภาพในการทำงานกับฐานข้อมูล ทำงานได้ง่ายและยืดหยุ่นมากขึ้น

ในบทนี้เนื้อหาจะเกี่ยวกับ Doctrine ORM ซึ่งเป้าหมายคือการให้คุณสามารถแมฟ object ไปยัง 
relational database ได้ (เช่น MySQL, PostgreSQL หรือ Microsoft SQL) 

A Simple Example: A Product

วิธีที่ง่ายที่สุดในการทำความเข้าใจว่า Doctrine ทำงานยังไงคือการ ดูการทำงานของมัน ในหัวข้อนี้คุณจะได้ config ฐานข้อมูล, สร้าง Product object, persist ข้อมูลลงในฐานข้อมูลและนำข้อมูลมาแสดง

Configuring the Database

คุณต้องกำหนดค่าการเชื่อมต่อฐานข้อมูล โดยปกติไฟล์ที่ใช้ในการ config อยู่ที่ app/config/parameters.yml

01

โดยปกติการกำหนดค่าทำผ่านไฟล์ parameters.yml โดยค่าที่กำหนดในไฟล์นี้จะถูกอ้างอิงไปยังไฟล์ config หลักเมื่อมีการตั้งค่า Doctrine

02

เมื่อแยกข้อมูลฐานข้อมูลลงในไฟล์ที่แยกจากกัน คุณสามารถเก็บไฟล์เวอร์ชันต่าง ๆ ไว้ในเซิร์ฟเวอร์แต่ละเครื่องได้อย่างง่ายดาย

เมื่อ Doctrine สามารถเชื่อมต่อกับฐานข้อมูลได้แล้ว คำสั่งข้างล่างนี้จะช่วยในการสร้างฐานข้อมูลเปล่าให้กับคุณ

03

Setting up the Database to be UTF8

การกำหนดค่าเริ่มต้น UTF8 สำหรับ MySQL ทำได้โดยเพิ่มการกำหนดค่าในไฟล์ (my.cnf)

04

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

05

Creating an Entity Class

สมมติว่าคุณกำลังสร้างแอปพลิเคชันที่ต้องแสดงผลิตภัณฑ์ ซึ่งคุณรู้อยู่แล้วว่าคุณต้องมี Product object เพื่อที่จะใช้แสดงถึงผลิตภัณฑ์เหล่านั้น ดังนั้น สร้างคลาสนี้ภายในไดเรกทอรีเอนทิตีของ AppBundle ของคุณ

06

class - มักเรียกว่า "entity" ซึ่งหมายถึงคลาสพื้นฐานที่เก็บข้อมูล - ซึ่งช่วยตอบสนองความต้องการทางธุรกิจของผลิตภัณฑ์ในแอ็พพลิเคชันของคุณ คลาสนี้ยังไม่สามารถ persist กับฐานข้อมูลได้

Add Mapping Information

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

07

คุณจะต้องทำการแม็ฟข้อมูลเหล่านี้ในรูปแบบของ "metadata" ซึ่งชุดของกฎเหล่านี้จะบอก Doctrine ว่าควรกำหนดคลาส Product และ property ของมันให้ตรงกับตารางฐานข้อมูลที่ต้องการอย่างไร โดย metadata นี้สามารถระบุได้หลายรูปแบบเช่น YAML, XML หรือระบุโดยตรงภายในคลาส Product ผ่าน DocBlock adnotations

08

โดย bundle หนึ่งสามารถระบุรูปแบบ metadata ได้แค่รูปแบบเดียว ไม่สามารถที่จะผสมรูปแบบ YMAL metadata
กับ annotated PHP entity ได้ 

--------------------------

ชื่อของตารางหากไม่ได้กำหนด ชื่อจะถูกกำหนดโดยอัตโนมัติตามชื่อของคลาส entity

Doctrine มีตัวเลือก field types ที่หลากหลายให้เลือกใช้ โดยสามารถดู field types ที่สามารถใช้ได้ในหัวข้อ Doctrine Field Types Reference

โปรดระวังถ้าชื่อคลาสของเอนทิตี (หรือ property) ตรงกับคำสงวนของ SQL เช่น GROUP หรือ USER ตัวอย่าง
เช่น ถ้าชื่อคลาสของเอนทิตีของคุณคือ Group โดยค่าเริ่มต้นชื่อตารางควรจะเป็น Group ซึ่งจะทำให้เกิดข้อผิดพลาด
ของ SQL ในบาง engines ของฐานข้อมูล 

Reserved SQL keywords documentation เป็นรายละเอียดเกี่ยวกับวิธีการหลีกเลี่ยงชื่อเหล่านี้อย่างเหมาะสม

Generating Getters and Setters

ถึงแม้ว่าตอนนี้ Doctrine จะรู้วิธีการ persist Product object ไปยังฐานข้อมูล แต่คลาสเหล่านั้นยังไม่สามารถทำงานได้จริง ๆ เนื่องจาก Product เป็นเพียงคลาส PHP ปกติที่มี private property คุณต้องสร้างเมธอด getter และ setter แบบ public (เช่น getName(), setName($name)) เพื่อให้สามารถเข้าถึง property ในแอปพลิเคชันได้

Creating the Database Tables/Schema

ตอนนี้คุณมีคลาส Product ที่สามารถใช้งานได้แล้ว พร้อมข้อมูลการแม็ฟที่ทำไว้ ซึ่งทำให้ Doctrine รู้ว่าจะ persist อย่างไร แต่ ณ ตอนนี้คุณยังไม่มีตาราง product ในฐานข้อมูล ซึ่ง Doctrine สามารถสร้างตารางฐานข้อมูลทั้งหมดที่จำเป็นสำหรับเอนทิตีทั้งหมดในแอปพลิเคชันของคุณได้ โดยใช้คำสั่ง

09

ถ้ามีการเพิ่ม property ใหม่ให้กับคลาส product และทำการแม็ฟเรียบร้อยแล้ว การเรียกใช้คำสั่งนี้ มันจะทำการ
เรียกใช้คำสั่ง "ALTER TABLE" เพื่อสร้างคอลัมน์ใหม่เพิ่มในตาราง product ให้

วิธีที่ดีในการใช้ประโยชน์จากฟังก์ชันนี้ก็คือใช้ผ่าน migrations ซึ่งช่วยให้คุณสามารถสร้างคำสั่ง SQL เหล่านี้
และจัดเก็บไว้ใน migration class นั้นสามารถทำให้การเรียกใช้คำสั่งเป็นระบบมากขึ้นบน production 
เซิร์ฟเวอร์ เนื่องจากการเราสามารถอัพเดท ติดตามการเปลี่ยนแปลงของฐานข้อมูลได้ ซึ่งทำให้ฐานข้อมูลมีความ
ปลอดภัยและน่าเชื่อถือ

ไม่ว่าอย่างไรก็ตาม การใช้คำสั่ง doctrine:schema:update ควรใช้ในระหว่างพัฒนาเท่านั้น ไม่ควรใช้บน 
production environment

Persisting Objects to the Database

เมื่อแม็ฟ Product entity ตรงกับตาราง product เรียบร้อยแล้ว ทำให้ตอนนี้คุณสามารถ persist Product object ลงในฐานข้อมูลได้แล้ว ด้วยการเพิ่มเมธอดเหล่านี้ลงใน DefaltController ดังตัวอย่าง

11

รายละเอียดเพิ่มเติมของโค้ดข้างบน

Fetching Objects from the Database

การดึง object จากฐานข้อมูลเป็นเรื่องง่ายมาก จากตัวอย่าง สมมติคุณกำหนด route ที่จะแสดง Product โดยอ้างอิงจากค่า id ของมัน

12

เมื่อคุณค้นหารายละเอียดต่าง ๆ ของ object คุณควรใช้สิ่งที่เรียกว่า "reposity" เสมอ โดย repository เป็นคลาส PHP ที่มีหน้าที่เกี่ยวกับการเรียกข้อมูลเอนทิตี้ของคลาสโดยเฉพาะ คุณสามารถเข้าถึง repository object สำหรับคลาสเอนทิตีได้ผ่านทาง

13

คุณสามารถใช้ syntax AppBundle:Product ซึ่งเป็นคีย์ลัดที่สามารถใช้ได้ทุกที่ใน Doctrine แทนการใช้ชื่อเต็ม
ของคลาสของเอนทิตี (เช่น AppBundle\Entity\Product) ตราบใดก็ตามที่เอนทิตีของคุณอยู่ภายใต้ Entity 
namespace ของ bundle มันจะทำงานได้

เมื่อคุณมี repository object คุณจะสามารถเข้าถึงทุกเมธอดที่มีประโยชน์ ได้ดังตัวอย่าง

14

โดยคุณสามารถนำข้อดีของเมธอด findBy() และ findOneBy มาใช้เพื่อดึง object บนเงื่อนไขที่มากขึ้นได้ เช่น

15

Updating an Object

เมื่อคุณดึง object มาจากฐานข้อมูลได้แล้ว การอัพเดทก็เป็นเรื่องง่ายเช่นกัน สมมติคุณมี route ที่ใช้ product id เพื่ออัพเดทใน controller ดังตัวอย่าง

16

การอัพเดท object มีสามขั้นตอน

  1. ดึง object จาก Doctrine
  2. แก้ไข object
  3. ใช้คำสั่ง flush() ของ entity manager

ไม่จำเป็นต้องใช้คำสั่ง $em->persist($product) โดยทั่วไปเมธอดนี้จะเป็นตัวบอก Doctrine เพื่อให้ manage หรือ "watch" $product object แต่ในกรณีนี้ เมื่อคุณดึง $product object มาจาก Doctrine มันจะรู้อยู่แล้วว่าต้อง managed

Deleting an Object

การลบ object ทำได้โดยใช้เมธอด remove() ของ entity manager

17

คุณอาจคาดไว้ว่าเมธอด remove() จะแจ้ง Doctrine ว่าคุณต้องการลบ object ออกจากฐานข้อมูล แต่การลบจริง ๆ จะเกิดขึ้นเมื่อคุณเรียกใช้เมธอด flush()

Querying for Objects

คุณได้เห็นแล้วว่า repository object ช่วยให้คุณ run basic query ได้อย่างไร

18

ที่นี้ Doctrine จะช่วยให้คุณสามารถเขียน query ที่ซับซ้อนมากขึ้นได้โดยใช้ Doctinre Query Language (DQL) ซึ่ง DQL มีความคล้ายกับ SQL เว้นแต่ว่าคุณควรจินตนาการว่าคุณกำลัง query object ของคลาสเอนทิตี มาหนึ่ง object หรือมากกว่านั้น (เช่น Product) แทนที่การ query แถวของตาราง (เช่น product)

ซึ่งการ query ของ Doctrine มีสองวิธีหลักคือ เขียน pure DQL query หรือใช้ Query Builder ของ Doctrine

Querying for Objects with DQL

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

19

ถ้าคุณรู้สึกพอใจกับการใช้ SQL แล้ว DQL ก็จะเป็นเรื่องธรรมดา ๆ แต่สิ่งที่แตกต่างมากที่สุดคือคุณต้องนึกถึงเงื่อนไขของการ selecting PHP object แทนที่การดึงแถวข้อมูลในฐานข้อมูล ด้วยเหตุนี้คุณจึง select from AppBundle:Product entity และตั้งคีย์ลัดเป็น p

เมธอด setParameter() จะทำงานเมื่อใช้กับ Doctrine มันเป็นความคิดที่ดีในการกำหนดค่าไว้ข้างนอกซึ่งเรียก
ว่า "placeholders" (จากตัวอย่างข้างบนคือ :price) เพื่อเป็นการป้องกัน SQL injection attacks

เมธอด getResult() จะส่งผลลัพธ์กลับมาเป็น array หากต้องการผลลัพธ์แค่หนึ่งสามารถใช้เมธอด getOneOrNullResult()

20

DQL syntax มีประสิทธิภาพมาก ซึ่งช่วยให้การ join กันระหว่างเอนทิตีเป็นเรื่องง่าย ดูรายละเอียดที่หัวข้อ relations

Querying for Objects Using Doctrine's Query Builder

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

21

QueryBuilder object จะบรรจุทุกเมธอดที่จำเป็นในการสร้าง query การเรียกใช้เมธอด getQuery() ทำให้ query builder ส่ง Query object ธรรมดากลับมา ซึ่งทำให้สามารถเข้าถึงผลลัพธ์ของการ query ได้

สำหรับข้อมูลเพิ่มเติม Query Builder ของ Doctrine ดุได้ที่ Query Builder

Organizing Custom Queries into Repository Classes

จาก query ทั้งหมดในหัวข้อก่อนหน้านี้ได้เขียนลงใน controller โดยตรง แต่เพื่อความเป็นระเบียบ Doctrine มีสามารถสร้าง special repository class ที่ช่วยให้คุณเก็บ query ทั้งหมดไว้ในที่เดียว ดูเพิ่มเติมได้ที่ How to Create custom Repository Classes

Configuration

Doctrine มีค่าที่กำหนดได้เองมาก แต่คุณอาจไม่จำเป็นต้องกังวลเกี่ยวกับ option นี้มากนัก หากต้องการข้อมูลเพิ่มเติมเกี่ยวกับการกำหนดค่า Doctrine โปรดดูที่หัวข้อ config refernce

Final Thoughts

ด้วย Doctrine คุณสามารถมุ่งเน้นไปที่ object ของคุณและการใช้พวกมันในแอปพลิเคชั่น จากนั้นค่อยนึกถึงเรื่อง persistence ฐานข้อมูล เนื่องจาก Doctrine ช่วยให้คุณใช้ PHP object เพื่อเก็บข้อมูลและการทำแม็ฟ metadata เพื่อแม็ฟ object data กับตารางฐานข้อมูล

Doctrine มีความสามารถอีกมากให้เรียนรู้ ได้แก่ relationships, complex queries, event listeners

source