MVC (Laravel) ที่จะเพิ่มตรรกะ


145

สมมติว่าเมื่อใดก็ตามที่ฉันดำเนินการ CRUD หรือแก้ไขความสัมพันธ์ในลักษณะเฉพาะฉันก็ต้องการทำอย่างอื่นด้วย เช่นเมื่อใดก็ตามที่มีคนเผยแพร่โพสต์ฉันก็ต้องการบันทึกบางสิ่งลงในตารางเพื่อการวิเคราะห์ อาจไม่ใช่ตัวอย่างที่ดีที่สุด แต่โดยทั่วไปมีฟังก์ชัน "จัดกลุ่ม" จำนวนมากนี้

ปกติฉันจะเห็นตรรกะประเภทนี้ใส่ไว้ในคอนโทรลเลอร์ ทั้งหมดนี้เป็นสิ่งที่ดีจนคุณต้องการสร้างฟังก์ชันนี้ซ้ำในหลาย ๆ ที่ เมื่อคุณเริ่มเข้าสู่บางส่วนการสร้าง API และสร้างเนื้อหาจำลองจะกลายเป็นปัญหาในการทำให้สิ่งต่างๆแห้ง

วิธีที่ฉันเห็นในการจัดการสิ่งนี้ ได้แก่ เหตุการณ์ที่เก็บไลบรารีและการเพิ่มลงในโมเดล นี่คือความเข้าใจของฉันเกี่ยวกับแต่ละข้อ:

บริการ:นี่คือที่ที่คนส่วนใหญ่อาจจะใส่รหัสนี้ ปัญหาหลักของฉันเกี่ยวกับบริการคือบางครั้งมันก็ยากที่จะพบฟังก์ชันเฉพาะในนั้นและฉันรู้สึกว่าพวกเขาลืมไปว่าเมื่อใดที่ผู้คนให้ความสำคัญกับการใช้ Eloquent ฉันจะรู้ว่าฉันต้องเรียกวิธีการpublishPost()ในห้องสมุดเมื่อฉันก็สามารถทำ$post->is_published = 1?

เงื่อนไขเดียวที่ฉันเห็นว่าใช้งานได้ดีคือถ้าคุณใช้บริการเท่านั้น (และเป็นการดีที่จะทำให้ Eloquent ไม่สามารถเข้าถึงได้จากคอนโทรลเลอร์ทั้งหมดด้วยกัน)

ในที่สุดดูเหมือนว่านี่จะเป็นการสร้างไฟล์ที่ไม่จำเป็นจำนวนมากหากโดยทั่วไปคำขอของคุณเป็นไปตามโครงสร้างแบบจำลองของคุณ

ที่เก็บ:จากสิ่งที่ฉันเข้าใจว่านี่เป็นเหมือนบริการ แต่มีอินเทอร์เฟซเพื่อให้คุณสามารถสลับไปมาระหว่าง ORM ซึ่งฉันไม่ต้องการ

เหตุการณ์:ฉันเห็นว่านี่เป็นระบบที่สง่างามที่สุดในแง่หนึ่งเพราะคุณรู้ว่าเหตุการณ์แบบจำลองของคุณมักจะถูกเรียกใช้วิธีการ Eloquent ดังนั้นคุณจึงสามารถเขียนคอนโทรลเลอร์ของคุณได้ตามปกติ ฉันเห็นว่าสิ่งเหล่านี้ยุ่งเหยิงและถ้าใครมีตัวอย่างของโครงการขนาดใหญ่ที่ใช้เหตุการณ์สำหรับการมีเพศสัมพันธ์ที่สำคัญฉันก็อยากดู

โมเดล:ตามเนื้อผ้าฉันมีคลาสที่ทำ CRUD และจัดการการมีเพศสัมพันธ์ที่สำคัญด้วย สิ่งนี้ทำให้ง่ายขึ้นจริง ๆ เพราะคุณรู้ฟังก์ชันการทำงานทั้งหมดรอบ ๆ CRUD + สิ่งที่ต้องทำก็อยู่ที่นั่น

เรียบง่าย แต่ในสถาปัตยกรรม MVC นี่ไม่ใช่สิ่งที่ฉันเห็นโดยปกติ ในแง่หนึ่งฉันชอบบริการนี้มากกว่าเพราะมันหาง่ายกว่าและมีไฟล์ให้ติดตามน้อยกว่า มันอาจจะไม่เป็นระเบียบสักหน่อย ฉันต้องการทราบความล้มเหลวของวิธีนี้และทำไมคนส่วนใหญ่ถึงไม่ทำ

ข้อดี / ข้อเสียของแต่ละวิธีคืออะไร? ฉันพลาดอะไรไปรึเปล่า?


3
คุณสามารถลดคำถามของคุณได้หรือไม่?
The Alpha

3
นอกจากนี้คุณสามารถตรวจสอบสิ่งนี้ได้
The Alpha

1
"ฉันจะรู้ได้อย่างไรว่าฉันต้องเรียกเมธอด PublishPost () ในไลบรารีเมื่อฉันทำได้ $ post-> is_published = 1" เอกสารประกอบ?
ceejayoz

หนึ่งในความสวยงามของการพูดเก่งและ ORMS มันง่ายกว่าไหมที่จะทำงานกับพวกเขาโดยไม่ต้องมีเอกสารมากมาย?
Sabrina Leggett

2
ขอบคุณสำหรับการโพสต์สิ่งนี้ ฉันกำลังต่อสู้กับปัญหาเดียวกันและพบว่าโพสต์ของคุณและคำตอบมีประโยชน์อย่างเหลือเชื่อ ในที่สุดฉันได้ตัดสินใจแล้วว่า Laravel ไม่ได้มีสถาปัตยกรรมที่ดีสำหรับสิ่งที่ขยายไปไกลกว่าเว็บไซต์ Ruby-on-Rails ที่รวดเร็วและสกปรก ลากไปทุกหนทุกแห่งความยากลำบากในการค้นหาฟังก์ชั่นคลาสและขยะเวทย์มนตร์อัตโนมัติมากมายทุกหนทุกแห่ง ORM ไม่เคยใช้งานได้และหากคุณใช้งานคุณน่าจะใช้ NoSQL
Alex Barker

คำตอบ:


179

ฉันคิดว่ารูปแบบ / สถาปัตยกรรมทั้งหมดที่คุณนำเสนอมีประโยชน์มากตราบเท่าที่คุณปฏิบัติตามหลักการSOLID

สำหรับสถานที่ที่จะเพิ่มตรรกะผมคิดว่ามันเป็นสิ่งสำคัญในการอ้างถึงSingle รับผิดชอบหลักการ นอกจากนี้คำตอบของฉันยังพิจารณาว่าคุณกำลังทำงานในโครงการขนาดกลาง / ใหญ่ หากเป็นโปรเจ็กต์การโยนอะไรบางอย่างบนหน้าให้ลืมคำตอบนี้และเพิ่มทั้งหมดลงในคอนโทรลเลอร์หรือโมเดล

คำตอบสั้น: อยู่ที่ไหนก็จะทำให้รู้สึกถึงคุณ (มีบริการ)

คำตอบยาว:

ตัวควบคุม : อะไรคือความรับผิดชอบของตัวควบคุม? แน่นอนว่าคุณสามารถใส่ตรรกะทั้งหมดลงในคอนโทรลเลอร์ได้ แต่นั่นเป็นความรับผิดชอบของคอนโทรลเลอร์หรือไม่? ฉันไม่คิดอย่างนั้น

สำหรับฉันผู้ควบคุมต้องได้รับคำขอและส่งคืนข้อมูลและนี่ไม่ใช่สถานที่สำหรับการตรวจสอบความถูกต้องเรียกวิธีการฐานข้อมูล ฯลฯ ..

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

ฉันคิดว่าโมเดลควรเป็นตัวแทนของเอนทิตี ด้วย Laravel ฉันเท่านั้นใช้คลาสรุ่นเพื่อเพิ่มสิ่งที่ชอบfillable, guarded, tableและความสัมพันธ์ (นี้เป็นเพราะผมใช้แบบ Repository มิฉะนั้นรูปแบบนอกจากนี้ยังจะมีsave, update, findวิธีการ ฯลฯ )

Repository (Repository Pattern) : ตอนแรกฉันสับสนมากกับสิ่งนี้ และเช่นเดียวกับคุณฉันคิดว่า "ฉันใช้ MySQL และนั่นก็เป็นเช่นนั้น"

อย่างไรก็ตามฉันได้ปรับสมดุลระหว่างข้อดีและข้อเสียของการใช้ Repository Pattern แล้วและตอนนี้ฉันก็ใช้มัน ฉันคิดว่าในตอนนี้ฉันจะต้องใช้ MySQL เท่านั้น แต่ถ้าสามปีจากนี้ฉันต้องเปลี่ยนเป็น MongoDB งานส่วนใหญ่เสร็จแล้ว ทั้งหมดนี้เป็นค่าใช้จ่ายของอินเทอร์เฟซพิเศษและไฟล์$app->bind(«interface», «repository»).

เหตุการณ์ ( รูปแบบผู้สังเกตการณ์ ):เหตุการณ์มีประโยชน์สำหรับสิ่งที่สามารถโยนในชั้นเรียนใดก็ได้ในเวลาใดก็ได้ ยกตัวอย่างเช่นการส่งการแจ้งเตือนไปยังผู้ใช้ เมื่อคุณต้องการคุณจะเริ่มกิจกรรมเพื่อส่งการแจ้งเตือนที่แอปพลิเคชันของคุณทุกระดับ จากนั้นคุณสามารถมีชั้นเรียนเช่นนี้UserNotificationEventsที่จัดการเหตุการณ์ที่เกิดขึ้นทั้งหมดของคุณสำหรับการแจ้งเตือนผู้ใช้

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

ลองดูตัวอย่างนี้: ช่วงสั้น ๆ ที่ผ่านมาฉันได้พัฒนาสิ่งต่างๆเช่น Google ฟอร์ม ผมเริ่มต้นด้วยCustomFormServiceและจบลงด้วยCustomFormService, CustomFormRender, CustomFieldService, CustomFieldRender, และCustomAnswerService CustomAnswerRenderทำไม? เพราะมันสมเหตุสมผลสำหรับฉัน หากคุณทำงานกับทีมคุณควรวางตรรกะของคุณในที่ที่เหมาะสมกับทีม

ข้อดีของการใช้ Services vs Controllers / Models คือคุณไม่ถูก จำกัด โดย Controller ตัวเดียวหรือ Model เดียว คุณสามารถสร้างบริการได้มากเท่าที่ต้องการตามการออกแบบและความต้องการของแอปพลิเคชันของคุณ เพิ่มข้อดีของการเรียกใช้บริการภายในแอปพลิเคชันของคุณทุกระดับ

มันใช้เวลานาน แต่ฉันอยากจะแสดงให้คุณเห็นว่าฉันมีโครงสร้างแอปพลิเคชันของฉันอย่างไร:

app/
    controllers/
    MyCompany/
        Composers/
        Exceptions/
        Models/
        Observers/
        Sanitizers/
        ServiceProviders/
        Services/
        Validators/
    views
    (...)

ฉันใช้แต่ละโฟลเดอร์สำหรับฟังก์ชันเฉพาะ ตัวอย่างเช่นValidatorsไดเร็กทอรีมีBaseValidatorคลาสที่รับผิดชอบในการประมวลผลการตรวจสอบความถูกต้องตาม$rulesและ$messagesของ validators ที่ระบุ (โดยปกติจะเป็นหนึ่งสำหรับแต่ละรุ่น) ฉันสามารถใส่รหัสนี้ในบริการได้อย่างง่ายดาย แต่มันก็สมเหตุสมผลสำหรับฉันที่จะมีโฟลเดอร์เฉพาะสำหรับสิ่งนี้แม้ว่าจะใช้เฉพาะในบริการก็ตาม (สำหรับตอนนี้)

ฉันขอแนะนำให้คุณอ่านบทความต่อไปนี้เนื่องจากอาจอธิบายสิ่งต่างๆได้ดีกว่าสำหรับคุณ:

ทำลายแม่พิมพ์โดย Dayle Rees (ผู้เขียน CodeBright): นี่คือที่ที่ฉันรวบรวมทั้งหมดเข้าด้วยกันแม้ว่าฉันจะเปลี่ยนบางสิ่งเพื่อให้เข้ากับความต้องการของฉัน

การแยกรหัสของคุณใน Laravel โดยใช้ Repositories and Servicesโดย Chris Goosey: โพสต์นี้อธิบายได้ดีว่า Service และ Repository Pattern คืออะไรและเข้ากันได้อย่างไร

Laracasts ยังมีRepositories Simplified and Single Responsibilityซึ่งเป็นแหล่งข้อมูลที่ดีพร้อมตัวอย่างที่ใช้ได้จริง (แม้ว่าคุณจะต้องจ่ายเงินก็ตาม)


3
คำอธิบายที่ดี นี่คือจุดที่ฉันยืนอยู่ในขณะนี้ - ในโครงการปัจจุบันฉันวางตรรกะทางธุรกิจของฉันไว้ในแบบจำลองและมันก็ใช้งานได้ดีจริงๆ แน่นอนเราต้องเหลวไหล SOLID สักหน่อย แต่มันยังไม่ทำให้เรามีปัญหา มันเร็วมันค่อนข้างสกปรก แต่จนถึงตอนนี้โครงการของเราสามารถบำรุงรักษาได้ดีมากเพราะมันแห้งมาก ตอนนี้ฉันก็โอเคที่จะยึดติดกับพวกเขาเพราะพวกเขาได้งานทำ แต่ในโครงการในอนาคตฉันอาจจะใช้อะไรก็ได้ที่เป็นมาตรฐานซึ่งดูเหมือนว่าที่เก็บจะกลายเป็น
Sabrina Leggett

2
ฉันดีใจที่คุณพบวิธีที่เหมาะสมกับคุณ เพียงแค่ต้องระวังด้วยสมมติฐานที่คุณทำในวันนี้ ฉันทำงานในโครงการมา 3 ปีขึ้นไปและลงเอยด้วยคอนโทรลเลอร์และโมเดลที่มีโค้ดมากกว่า 5,000 บรรทัด โชคดีกับโครงการของคุณ.
Luís Cruz

ยังค่อนข้างสกปรก แต่ฉันกำลังคิดที่จะใช้ลักษณะเพื่อหลีกเลี่ยงไม่ให้นางแบบมีขนาดใหญ่ ด้วยวิธีนี้ฉันสามารถแยกพวกเขาออกเล็กน้อย
Sabrina Leggett

บทความนี้อธิบายได้ดีเมื่อใดที่ควรใช้บริการ ในตัวอย่างแบบฟอร์มของคุณมันสมเหตุสมผลที่จะใช้บริการ แต่เขาอธิบายว่าเขาทำอย่างไรซึ่งเมื่อตรรกะเกี่ยวข้องโดยตรงกับแบบจำลองที่เขาวางไว้ในแบบจำลองนั้น justinweiss.com/articles/where-do-you-put-your-code
Sabrina Leggett

ฉันชอบคำอธิบายมาก มีคำถามหนึ่งข้อที่ฉันมี: คุณบอกว่าอย่าใส่การตรวจสอบความถูกต้องในตัวควบคุมแล้วคุณคิดว่าที่ไหนดีที่สุดในการตรวจสอบความถูกต้อง? หลายคนแนะนำให้ใส่ลงในคลาสคำขอเพิ่มเติม (และสิ่งที่เราทำอยู่ในปัจจุบัน) แต่ถ้าฉันไม่เพียงต้องการตรวจสอบความถูกต้องตามคำขอ http แต่ยังใช้คำสั่งช่างฝีมือด้วย ฯลฯ มันเป็นสถานที่ที่ดีจริงๆหรือ
kingshark

25

ฉันต้องการโพสต์คำตอบสำหรับคำถามของฉันเอง ฉันสามารถพูดคุยเกี่ยวกับเรื่องนี้เป็นเวลาหลายวัน แต่ฉันจะพยายามโพสต์สิ่งนี้อย่างรวดเร็วเพื่อให้แน่ใจว่าจะได้รับข้อมูล

ฉันลงเอยด้วยการใช้โครงสร้างที่มีอยู่ที่ Laravel มีให้ซึ่งหมายความว่าฉันเก็บไฟล์ของฉันไว้เป็น Model, View และ Controller เป็นหลัก ฉันยังมีโฟลเดอร์ Libraries สำหรับส่วนประกอบที่ใช้ซ้ำได้ซึ่งไม่ใช่รุ่นจริงๆ

ฉันไม่ได้ห่อโมเดลของฉันในการให้บริการ / ห้องสมุด เหตุผลทั้งหมดที่ให้มาไม่ได้ทำให้ฉันมั่นใจได้ 100% ถึงประโยชน์ของการใช้บริการ แม้ว่าฉันอาจจะคิดผิด แต่เท่าที่ฉันเห็นมันส่งผลให้มีไฟล์ว่างเปล่าจำนวนมากที่ฉันต้องสร้างและสลับไปมาระหว่างการทำงานกับโมเดลและยังลดประโยชน์ของการใช้รูปแบบที่คมชัด (โดยเฉพาะอย่างยิ่งเมื่อพูดถึงโมเดล RETRIEVING เช่นการใช้เลขหน้าขอบเขต ฯลฯ )

ฉันใส่ตรรกะทางธุรกิจไว้ในโมเดลและเข้าถึงฝีปากโดยตรงจากคอนโทรลเลอร์ของฉัน ฉันใช้หลายวิธีเพื่อให้แน่ใจว่าตรรกะทางธุรกิจไม่ถูกข้าม:

  • Accessors และ mutators: Laravel มี accessors และ mutators ที่ยอดเยี่ยม หากฉันต้องการดำเนินการเมื่อใดก็ตามที่โพสต์ถูกย้ายจากแบบร่างไปเผยแพร่ฉันสามารถเรียกสิ่งนี้ได้โดยสร้างฟังก์ชัน setIsPublishedAttribute และรวมตรรกะไว้ในนั้น
  • การแทนที่ Create / Update etc:คุณสามารถแทนที่ Eloquent method ในโมเดลของคุณเพื่อรวมฟังก์ชันที่กำหนดเองได้ ด้วยวิธีนี้คุณสามารถเรียกใช้ฟังก์ชันการทำงานของ CRUD ได้ แก้ไข: ฉันคิดว่ามีข้อบกพร่องในการลบล้างการสร้างใน Laravel เวอร์ชันใหม่กว่า (ดังนั้นฉันจึงใช้เหตุการณ์ที่ลงทะเบียนในการบูตแล้ว)
  • การตรวจสอบความถูกต้อง:ฉันขอการตรวจสอบความถูกต้องของฉันในลักษณะเดียวกันเช่นฉันจะเรียกใช้การตรวจสอบความถูกต้องโดยการแทนที่ฟังก์ชัน CRUD และตัวเข้าถึง / ตัวกลายพันธุ์ด้วยหากจำเป็น ดู Esensi หรือ dwightwatson / การตรวจสอบความถูกต้องสำหรับข้อมูลเพิ่มเติม
  • Magic Methods:ฉันใช้เมธอด __get และ __set ของโมเดลของฉันเพื่อเชื่อมต่อกับฟังก์ชันการทำงานตามความเหมาะสม
  • การขยาย Eloquent:หากมีการดำเนินการที่คุณต้องการดำเนินการในการอัปเดต / สร้างทั้งหมดคุณสามารถขยายความคมชัดและนำไปใช้กับหลายรุ่นได้
  • เหตุการณ์:นี่เป็นเรื่องตรงไปตรงมาและตกลงกันโดยทั่วไปว่าจะทำเช่นนี้เช่นกัน ข้อเสียเปรียบที่ใหญ่ที่สุดของเหตุการณ์ที่ฉันคิดคือข้อยกเว้นนั้นยากที่จะติดตาม (อาจไม่ใช่กรณีใหม่กับระบบเหตุการณ์ใหม่ของ Laravel) ฉันยังชอบจัดกลุ่มกิจกรรมของฉันตามสิ่งที่พวกเขาทำแทนที่จะเรียกว่า ... เช่นมีสมาชิก MailSender ที่คอยฟังเหตุการณ์ที่ส่งจดหมาย
  • การเพิ่ม Pivot / BelongsToMany เหตุการณ์: สิ่งหนึ่งที่ฉันต่อสู้มายาวนานที่สุดคือการแนบพฤติกรรมกับการปรับเปลี่ยนความสัมพันธ์ belongToMany เช่นดำเนินการทุกครั้งที่ผู้ใช้เข้าร่วมกลุ่ม ฉันเกือบจะขัดไลบรารีที่กำหนดเองสำหรับสิ่งนี้เสร็จแล้ว ฉันยังไม่ได้เผยแพร่ แต่มันใช้งานได้! จะพยายามโพสต์ลิงค์เร็ว ๆ นี้ แก้ไขฉันลงเอยด้วยการเปลี่ยนทุกอย่างให้เป็นแบบจำลองปกติและชีวิตของฉันง่ายขึ้นมาก ...

ตอบข้อกังวลของผู้คนเกี่ยวกับการใช้โมเดล:

  • องค์กร:ใช่ถ้าคุณใส่ลอจิกในโมเดลมากขึ้นมันอาจจะยาวขึ้น แต่โดยทั่วไปแล้วฉันพบว่า 75% ของโมเดลของฉันยังค่อนข้างเล็ก หากฉันเลือกที่จะจัดระเบียบสิ่งที่ใหญ่กว่าฉันสามารถทำได้โดยใช้ลักษณะ (เช่นสร้างโฟลเดอร์สำหรับโมเดลที่มีไฟล์อื่น ๆ เช่น PostScopes, PostAccessors, PostValidation และอื่น ๆ ตามต้องการ) ฉันรู้ว่านี่ไม่จำเป็นต้องมีลักษณะเฉพาะ แต่ระบบนี้ทำงานได้โดยไม่มีปัญหา

หมายเหตุเพิ่มเติม:ฉันรู้สึกว่าการห่อโมเดลของคุณในบริการนั้นเหมือนกับการมีมีดสวิสที่มีเครื่องมือมากมายและการสร้างมีดอีกอันรอบ ๆ มันโดยพื้นฐานแล้วมันก็เหมือนกันใช่ไหม ใช่บางครั้งคุณอาจต้องการเทปใบมีดออกหรือตรวจสอบให้แน่ใจว่าใช้ใบมีดสองใบร่วมกัน ... แต่โดยทั่วไปแล้วจะมีวิธีอื่นในการทำ ...

เมื่อใดควรใช้บริการ : บทความนี้อธิบายตัวอย่างที่ดีสำหรับการใช้บริการเมื่อใด ( คำใบ้: ไม่บ่อยนัก ) เขากล่าวว่าโดยพื้นฐานแล้วเมื่อวัตถุของคุณใช้โมเดลหรือโมเดลหลายตัวในส่วนที่แปลกประหลาดของวงจรชีวิตของพวกเขามันก็สมเหตุสมผล http://www.justinweiss.com/articles/where-do-you-put-your-code/


2
ความคิดที่น่าสนใจและถูกต้อง แต่ฉันอยากรู้ - คุณจะทดสอบตรรกะทางธุรกิจของคุณได้อย่างไรว่ามันเชื่อมโยงกับโมเดลที่เชื่อมโยงกับ Eloquent ซึ่งเชื่อมโยงกับฐานข้อมูลหรือไม่
JustAMartin

code.tutsplus.com/tutorials/…หรือจะใช้อีเวนต์อย่างที่บอกก็ได้ถ้าอยากแยกย่อยออกไป
Sabrina Leggett

1
@JustAMartin แน่ใจหรือว่าใช้ฐานข้อมูลในการทดสอบหน่วยไม่ได้? เหตุผลที่จะไม่ทำคืออะไร? หลายคนยอมรับว่าบ่อยครั้งที่จะใช้ฐานข้อมูลในการทดสอบหน่วย (รวมถึง Martin Fowler, martinfowler.com/bliki/UnitTest.html : "ฉันไม่ถือว่าการใช้ทรัพยากรภายนอกเป็นสองเท่าเป็นกฎสัมบูรณ์หากการพูดคุยกับทรัพยากรมีความเสถียรและเร็วพอสำหรับคุณก็ไม่มีเหตุผลที่จะไม่ทำ ในการทดสอบหน่วยของคุณ ")
Alex P.

@ AlexP11223 ใช่นั่นเข้าท่า ฉันพยายามรวม SQLite เป็นฐานข้อมูลทดสอบของฉันและโดยทั่วไปแล้วมันก็ใช้ได้ดีแม้ว่า SQLite จะมีข้อ จำกัด ร้ายแรงบางอย่างที่ต้องคำนึงถึงในการย้าย Laravel และการสืบค้นแบบกำหนดเอง (ถ้ามี) แน่นอนว่าสิ่งเหล่านั้นไม่ใช่การทดสอบหน่วยอย่างเคร่งครัด แต่เป็นการทดสอบการทำงาน แต่ก็มีประสิทธิภาพมากกว่าด้วยวิธีนั้น อย่างไรก็ตามหากคุณต้องการทดสอบโมเดลของคุณแบบแยกส่วนอย่างสมบูรณ์ (เป็นการทดสอบหน่วยที่เข้มงวด) อาจต้องใช้รหัสเพิ่มเติมจำนวนมากที่เห็นได้ชัด (ล้อเลียนเป็นต้น)
JustAMartin

22

สิ่งที่ผมใช้ในการทำเพื่อสร้างตรรกะระหว่างตัวควบคุมและรูปแบบคือการสร้างชั้นบริการ โดยทั่วไปนี่คือขั้นตอนของฉันสำหรับการดำเนินการใด ๆ ภายในแอปของฉัน:

  1. คอนโทรลเลอร์รับการดำเนินการที่ร้องขอของผู้ใช้และส่งพารามิเตอร์และมอบหมายทุกอย่างให้กับคลาสบริการ
  2. คลาสบริการทำตรรกะทั้งหมดที่เกี่ยวข้องกับการดำเนินการ: การตรวจสอบอินพุตการบันทึกเหตุการณ์การดำเนินการฐานข้อมูล ฯลฯ ...
  3. โมเดลเก็บข้อมูลของฟิลด์การแปลงข้อมูลและคำจำกัดความของการตรวจสอบคุณสมบัติ

นี่คือวิธีที่ฉันทำ:

นี่คือวิธีการของคอนโทรลเลอร์ในการสร้างบางสิ่ง:

public function processCreateCongregation()
{
    // Get input data.
    $congregation                 = new Congregation;
    $congregation->name           = Input::get('name');
    $congregation->address        = Input::get('address');
    $congregation->pm_day_of_week = Input::get('pm_day_of_week');
    $pmHours                      = Input::get('pm_datetime_hours');
    $pmMinutes                    = Input::get('pm_datetime_minutes');
    $congregation->pm_datetime    = Carbon::createFromTime($pmHours, $pmMinutes, 0);

    // Delegates actual operation to service.
    try
    {
        CongregationService::createCongregation($congregation);
        $this->success(trans('messages.congregationCreated'));
        return Redirect::route('congregations.list');
    }
    catch (ValidationException $e)
    {
        // Catch validation errors thrown by service operation.
        return Redirect::route('congregations.create')
            ->withInput(Input::all())
            ->withErrors($e->getValidator());
    }
    catch (Exception $e)
    {
        // Catch any unexpected exception.
        return $this->unexpected($e);
    }
}

นี่คือคลาสบริการที่ใช้ตรรกะที่เกี่ยวข้องกับการดำเนินการ:

public static function createCongregation(Congregation $congregation)
{
    // Log the operation.
    Log::info('Create congregation.', compact('congregation'));

    // Validate data.
    $validator = $congregation->getValidator();

    if ($validator->fails())
    {
        throw new ValidationException($validator);
    }

    // Save to the database.
    $congregation->created_by = Auth::user()->id;
    $congregation->updated_by = Auth::user()->id;

    $congregation->save();
}

และนี่คือโมเดลของฉัน:

class Congregation extends Eloquent
{
    protected $table = 'congregations';

    public function getValidator()
    {
        $data = array(
            'name' => $this->name,
            'address' => $this->address,
            'pm_day_of_week' => $this->pm_day_of_week,
            'pm_datetime' => $this->pm_datetime,
        );

        $rules = array(
            'name' => ['required', 'unique:congregations'],
            'address' => ['required'],
            'pm_day_of_week' => ['required', 'integer', 'between:0,6'],
            'pm_datetime' => ['required', 'regex:/([01]?[0-9]|2[0-3]):[0-5]?[0-9]:[0-5][0-9]/'],
        );

        return Validator::make($data, $rules);
    }

    public function getDates()
    {
        return array_merge_recursive(parent::getDates(), array(
            'pm_datetime',
            'cbs_datetime',
        ));
    }
}

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับวิธีนี้ฉันใช้ในการจัดระเบียบโค้ดสำหรับแอป Laravel: https://github.com/rmariuzzo/Pitimi


ดูเหมือนว่าบริการคือสิ่งที่ฉันเรียกว่าห้องสมุดในโพสต์ของฉัน ฉันคิดว่านี่ดีกว่าที่เก็บถ้าคุณไม่จำเป็นต้องใช้หลาย ORMS แต่ปัญหาคือคุณต้องย้ายโปรเจ็กต์ทั้งหมดของคุณ (ซึ่งคุณไม่ต้องทำอะไรกับเหตุการณ์) และดูเหมือนว่า มันจบลงด้วยการจำลองโครงสร้างโมเดลดังนั้นคุณจึงมีไฟล์พิเศษเหล่านี้ทั้งหมด ทำไมไม่รวมไว้ในแบบจำลอง? อย่างน้อยวิธีนี้คุณก็ไม่มีไฟล์พิเศษ
Sabrina Leggett

นั่นเป็นคำถามที่น่าสนใจ @SabrinaGelbart ฉันได้รับการสอนให้ปล่อยให้แบบจำลองเป็นตัวแทนของเอนทิตีฐานข้อมูลและไม่ถือตรรกะใด ๆ นั่นคือเหตุผลที่ฉันสร้างไฟล์พิเศษเหล่านั้นที่ชื่อว่าบริการ: เพื่อเก็บตรรกะและการดำเนินการพิเศษใด ๆ ฉันไม่แน่ใจว่าความหมายทั้งหมดของเหตุการณ์ที่คุณอธิบายไว้ก่อนหน้านี้คืออะไร แต่ฉันคิดว่าด้วยบริการและการใช้งานกิจกรรมของ Laravelเราสามารถสร้างวิธีการบริการทั้งหมดเพื่อให้เหตุการณ์เริ่มต้นและสิ้นสุดได้ วิธีนี้สามารถแยกเหตุการณ์ออกจากตรรกะได้อย่างสมบูรณ์ คุณคิดอย่างไร?
Rubens Mariuzzo

ฉันได้รับการสอนเกี่ยวกับแบบจำลองเช่นกัน ... จะดีไหมหากได้รับคำอธิบายที่ดีว่าทำไม (อาจเป็นปัญหาการพึ่งพา)
Sabrina Leggett

ฉันชอบแนวทางนี้! ฉันค้นหาอินเทอร์เน็ตเพื่อให้ทราบว่าฉันควรจัดการตรรกะของโมเดลอย่างไรดูที่ Repositories แต่ดูเหมือนซับซ้อนเกินไปและไร้ประโยชน์สำหรับการใช้งานเล็กน้อย บริการเป็นความคิดที่ดี คำถามของฉันคือหลังจากสร้างโฟลเดอร์ Services ในโฟลเดอร์แอพแล้วคุณต้องรวมไว้ใน bootstrap / start.php หรือที่ใดก็ได้สำหรับการบูตเพราะฉันมองข้าม git ของคุณไม่พบ? จจ. แอปนี้พร้อมใช้งานโดยอัตโนมัติหรือไม่ ดังนั้นเราสามารถใช้ CongregationService :: getCongregations (); ??
Oguzhan

1
หากสิ่งที่คุณทำคือ$congregation->save();คุณอาจไม่ต้องการ Repositories อย่างไรก็ตามคุณอาจเห็นความต้องการในการเข้าถึงข้อมูลของคุณเพิ่มขึ้นเมื่อเวลาผ่านไป คุณอาจเริ่มมีความต้องการ$congregation->destroyByUser()หรือ$congregationUsers->findByName($arrayOfSelectedFields);อื่น ๆ ทำไมไม่เลิกจับคู่บริการของคุณจากความต้องการในการเข้าถึงข้อมูล ปล่อยให้แอปที่เหลือของคุณทำงานร่วมกับวัตถุ / อาร์เรย์ที่ส่งคืนจาก repos และจัดการกับการจัดการ / การจัดรูปแบบ / ฯลฯ ... repo ของคุณจะเติบโตขึ้น (แต่แบ่งออกเป็นไฟล์ต่างๆในที่สุดความซับซ้อนของโครงการจะต้องอยู่ที่ใดที่หนึ่ง)
prograhammer

13

ในความคิดของฉัน Laravel มีตัวเลือกมากมายให้คุณจัดเก็บตรรกะทางธุรกิจของคุณ

คำตอบสั้น ๆ :

  • ใช้Requestวัตถุของ laravel เพื่อตรวจสอบความถูกต้องของข้อมูลที่คุณป้อนโดยอัตโนมัติจากนั้นคงข้อมูลไว้ในคำขอ (สร้างแบบจำลอง) เนื่องจากการป้อนข้อมูลของผู้ใช้ทั้งหมดมีอยู่ในคำขอโดยตรงฉันเชื่อว่าควรดำเนินการที่นี่
  • ใช้Jobวัตถุของ laravel เพื่อทำงานที่ต้องใช้ส่วนประกอบแต่ละชิ้นจากนั้นก็จัดส่งไป ฉันคิดว่าJobชั้นเรียนบริการครอบคลุม พวกเขาปฏิบัติงานเช่นตรรกะทางธุรกิจ

คำตอบยาว (เอ่อ):

ใช้ Respositories เมื่อจำเป็น: Repository ถูกผูกไว้ให้ล้นเกินและส่วนใหญ่จะใช้เป็นเพียงaccessorโมเดลเท่านั้น ฉันรู้สึกว่าพวกเขามีประโยชน์อย่างแน่นอน แต่ถ้าคุณไม่ได้พัฒนาแอปพลิเคชันขนาดใหญ่ที่ต้องการความยืดหยุ่นในระดับนั้นเพื่อให้คุณสามารถทิ้ง laravel ได้ทั้งหมดอย่าอยู่ห่างจากที่เก็บ คุณจะขอบคุณตัวเองในภายหลังและรหัสของคุณจะตรงไปตรงมามากขึ้น

ถามตัวเองว่ามีความเป็นไปได้หรือไม่ที่คุณกำลังจะเปลี่ยน PHP framework หรือเป็นประเภทฐานข้อมูลที่ laravel ไม่รองรับ

หากคำตอบของคุณคือ "น่าจะไม่ใช่" อย่าใช้รูปแบบที่เก็บ

นอกเหนือจากข้างต้นโปรดอย่าตบรูปแบบที่อยู่ด้านบนของ ORM ที่ยอดเยี่ยมเช่น Eloquent คุณแค่เพิ่มความซับซ้อนที่ไม่จำเป็นและมันจะไม่เป็นประโยชน์กับคุณเลย

ใช้ประโยชน์จากบริการเท่าที่จำเป็น: ชั้นเรียนบริการสำหรับฉันเป็นเพียงที่เก็บตรรกะทางธุรกิจเพื่อดำเนินงานเฉพาะด้วยการอ้างอิงที่กำหนด Laravel มีสิ่งเหล่านี้นอกกรอบเรียกว่า 'งาน' และมีความยืดหยุ่นมากกว่าคลาสบริการที่กำหนดเอง

ฉันรู้สึกว่า Laravel มีวิธีแก้ปัญหาMVCตรรกะรอบด้าน เป็นเพียงเรื่องหรือองค์กร

ตัวอย่าง:

คำขอ :

namespace App\Http\Requests;

use App\Post;
use App\Jobs\PostNotifier;
use App\Events\PostWasCreated;
use App\Http\Requests\Request;

class PostRequest extends Request
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'title'       => 'required',
            'description' => 'required'
        ];
    }

    /**
     * Save the post.
     *
     * @param Post $post
     *
     * @return bool
     */
    public function persist(Post $post)
    {
        if (!$post->exists) {
            // If the post doesn't exist, we'll assign the
            // post as created by the current user.
            $post->user_id = auth()->id();
        }

        $post->title = $this->title;
        $post->description = $this->description;

        // Perform other tasks, maybe fire an event, dispatch a job.

        if ($post->save()) {
            // Maybe we'll fire an event here that we can catch somewhere else that
            // needs to know when a post was created.
            event(new PostWasCreated($post));

            // Maybe we'll notify some users of the new post as well.
            dispatch(new PostNotifier($post));

            return true;
        }

        return false;
    }
}

ตัวควบคุม :

namespace App\Http\Controllers;

use App\Post;
use App\Http\Requests\PostRequest;

class PostController extends Controller
{

   /**
    * Creates a new post.
    *
    * @return string
    */
    public function store(PostRequest $request)
    {
        if ($request->persist(new Post())) {
            flash()->success('Successfully created new post!');
        } else {
            flash()->error('There was an issue creating a post. Please try again.');
        }

        return redirect()->back();
    }

   /**
    * Updates a post.
    *
    * @return string
    */
    public function update(PostRequest $request, $id)
    {
        $post = Post::findOrFail($id);

        if ($request->persist($post)) {
            flash()->success('Successfully updated post!');
        } else {
            flash()->error('There was an issue updating this post. Please try again.');
        }

        return redirect()->back();
    }
}

ในตัวอย่างด้านบนอินพุตคำขอจะได้รับการตรวจสอบความถูกต้องโดยอัตโนมัติและสิ่งที่เราต้องทำคือเรียกใช้วิธีการคงอยู่และส่งผ่านในโพสต์ใหม่ ฉันคิดว่าความสามารถในการอ่านและการบำรุงรักษาควรเป็นสิ่งสำคัญกว่ารูปแบบการออกแบบที่ซับซ้อนและไม่จำเป็น

จากนั้นคุณสามารถใช้วิธีการคงอยู่เดิมในการอัปเดตโพสต์ได้เช่นกันเนื่องจากเราสามารถตรวจสอบได้ว่ามีโพสต์อยู่แล้วหรือไม่และดำเนินการสลับตรรกะเมื่อจำเป็น


แต่ - งานไม่ "ควร" อยู่ในคิว? บางครั้งเราอาจต้องการให้เข้าคิว แต่ไม่ใช่ตลอดเวลา ทำไมไม่ใช้คำสั่งแทนล่ะ? จะเป็นอย่างไรถ้าคุณต้องการเขียนตรรกะทางธุรกิจที่อาจดำเนินการเป็นคำสั่งหรือเหตุการณ์หรืออยู่ในคิว
Sabrina Leggett

1
งานไม่จำเป็นต้องเข้าคิว คุณระบุสิ่งนั้นโดยการนำอินเทอร์เฟซไปใช้กับงานShouldQueueที่ Laravel มีให้ หากคุณต้องการเขียน Business logic ในคำสั่งหรือเหตุการณ์เพียงแค่เริ่มงานภายในเหตุการณ์ / คำสั่งเหล่านั้น งาน Laravels มีความยืดหยุ่นสูง แต่สุดท้ายแล้วก็เป็นเพียงชั้นเรียนบริการธรรมดา
Steve Bauman
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.