ฉันคิดว่าฉันจะหยุดตอบคำถามของฉันเอง สิ่งต่อไปนี้เป็นเพียงวิธีหนึ่งในการแก้ปัญหาข้อ 1-3 ในคำถามเดิมของฉัน
ข้อจำกัดความรับผิดชอบ: ฉันอาจไม่ใช้คำที่ถูกต้องเสมอเมื่ออธิบายรูปแบบหรือเทคนิค ขอโทษสำหรับสิ่งนั้น.
เป้าหมาย:
Users
สร้างตัวอย่างที่สมบูรณ์แบบของตัวควบคุมขั้นพื้นฐานสำหรับการดูและการแก้ไข
- รหัสทั้งหมดจะต้องสามารถทดสอบได้อย่างสมบูรณ์และเยาะเย้ย
- ผู้ควบคุมไม่ควรทราบว่าเก็บข้อมูลไว้ที่ไหน (หมายถึงสามารถเปลี่ยนแปลงได้)
- ตัวอย่างเพื่อแสดงการใช้งาน SQL (โดยทั่วไป)
- เพื่อประสิทธิภาพสูงสุดผู้ควบคุมควรได้รับข้อมูลที่ต้องการเท่านั้น - ไม่มีฟิลด์เพิ่มเติม
- การใช้งานควรใช้ประโยชน์จาก mapper ข้อมูลบางประเภทเพื่อความสะดวกในการพัฒนา
- การใช้งานควรมีความสามารถในการค้นหาข้อมูลที่ซับซ้อน
การแก้ไขปัญหา
ฉันแบ่งการโต้ตอบ (ฐานข้อมูล) ที่เก็บข้อมูลถาวรของฉันออกเป็นสองประเภท: R (อ่าน) และCUD (สร้างอัปเดตลบ) ประสบการณ์ของฉันคือการอ่านเป็นสิ่งที่ทำให้แอปพลิเคชันช้าลงจริง ๆ และในขณะที่การจัดการข้อมูล (CUD) ช้าลง แต่มันก็เกิดขึ้นบ่อยครั้งกว่ามากและมีความกังวลน้อยกว่ามาก
CUD (สร้างอัปเดตลบ) เป็นเรื่องง่าย นี้จะเกี่ยวข้องกับการทำงานร่วมกับที่เกิดขึ้นจริงรุ่นซึ่งจะผ่านไปแล้วฉันจะRepositories
ติดตาสำหรับ หมายเหตุที่เก็บของฉันจะยังคงให้วิธีการอ่าน แต่เพียงสำหรับการสร้างวัตถุไม่แสดง เพิ่มเติมในภายหลัง
R (อ่าน) ไม่ใช่เรื่องง่าย ไม่มีรูปแบบที่นี่เพียงวัตถุที่คุ้มค่า ใช้อาร์เรย์ถ้าคุณชอบ วัตถุเหล่านี้อาจเป็นตัวแทนของรูปแบบเดียวหรือผสมผสานหลายรูปแบบอะไรก็ได้ สิ่งเหล่านี้ไม่ได้น่าสนใจมากนัก แต่เป็นวิธีที่สร้างขึ้นมา Query Objects
ฉันใช้สิ่งที่ฉันเรียก
รหัส:
รูปแบบของผู้ใช้
มาเริ่มกันง่ายๆกับโมเดลผู้ใช้พื้นฐานของเรา โปรดทราบว่าไม่มีการขยาย ORM หรือฐานข้อมูลเลย สง่าราศีแบบบริสุทธิ์เท่านั้น เพิ่ม getters, setters, validation หรืออะไรก็ตามของคุณ
class User
{
public $id;
public $first_name;
public $last_name;
public $gender;
public $email;
public $password;
}
ส่วนต่อประสาน Repository
ก่อนที่ฉันจะสร้างที่เก็บผู้ใช้ของฉันฉันต้องการที่จะสร้างส่วนต่อประสานที่เก็บของฉัน สิ่งนี้จะกำหนด "สัญญา" ที่ที่เก็บต้องปฏิบัติตามเพื่อให้ผู้ควบคุมของฉันใช้ โปรดจำไว้ว่าคอนโทรลเลอร์ของฉันจะไม่ทราบว่าข้อมูลถูกเก็บไว้ที่ไหน
โปรดทราบว่าที่เก็บของฉันจะมีเพียงสามวิธีเท่านั้น save()
วิธีการเป็นผู้รับผิดชอบทั้งการสร้างและปรับปรุงผู้ใช้เพียงแค่ขึ้นอยู่กับว่าหรือไม่วัตถุที่ผู้ใช้มีชุดประจำตัวประชาชน
interface UserRepositoryInterface
{
public function find($id);
public function save(User $user);
public function remove(User $user);
}
การใช้งาน Repository ของ SQL
ตอนนี้เพื่อสร้างการนำไปใช้งานของส่วนต่อประสาน ดังที่กล่าวไว้ตัวอย่างของฉันจะอยู่กับฐานข้อมูล SQL หมายเหตุการใช้mapper ข้อมูลเพื่อป้องกันไม่ให้ต้องเขียนแบบสอบถาม SQL ซ้ำ
class SQLUserRepository implements UserRepositoryInterface
{
protected $db;
public function __construct(Database $db)
{
$this->db = $db;
}
public function find($id)
{
// Find a record with the id = $id
// from the 'users' table
// and return it as a User object
return $this->db->find($id, 'users', 'User');
}
public function save(User $user)
{
// Insert or update the $user
// in the 'users' table
$this->db->save($user, 'users');
}
public function remove(User $user)
{
// Remove the $user
// from the 'users' table
$this->db->remove($user, 'users');
}
}
อินเตอร์เฟซวัตถุแบบสอบถาม
ขณะนี้ด้วยCUD (สร้างอัพเดตลบ) ที่เก็บข้อมูลของเราดูแลเราสามารถมุ่งเน้นไปที่R (อ่าน) แบบสอบถามวัตถุเป็นเพียงการห่อหุ้มของตรรกะการค้นหาข้อมูลบางประเภท พวกเขาไม่ใช่ผู้สร้างแบบสอบถาม โดยการทำให้เป็นนามธรรมเหมือนกับที่เก็บของเราเราสามารถเปลี่ยนการใช้งานและทดสอบได้ง่ายขึ้น ตัวอย่างของวัตถุแบบสอบถามอาจจะมีAllUsersQuery
หรือหรือแม้กระทั่งAllActiveUsersQuery
MostCommonUserFirstNames
คุณอาจกำลังคิดว่า "ฉันไม่สามารถสร้างวิธีการในที่เก็บข้อมูลของฉันสำหรับการค้นหาเหล่านั้นได้หรือ" ใช่ แต่นี่คือเหตุผลที่ฉันไม่ทำสิ่งนี้:
- ที่เก็บของฉันมีไว้สำหรับทำงานกับโมเดลวัตถุ ในแอปโลกแห่งความจริงเหตุใดฉันจึงต้องได้รับ
password
ข้อมูลหากฉันต้องการแสดงรายชื่อผู้ใช้ทั้งหมดของฉัน
- ที่เก็บมักจะเป็นรูปแบบที่เฉพาะเจาะจง แต่แบบสอบถามมักจะเกี่ยวข้องมากกว่าหนึ่งรูปแบบ ดังนั้นที่เก็บที่คุณใส่วิธีการของคุณ?
- สิ่งนี้ทำให้ที่เก็บของฉันง่ายมาก - ไม่ใช่วิธีการเรียนที่ป่อง
- แบบสอบถามทั้งหมดจะถูกจัดระเบียบเป็นคลาสของตนเอง
- จริงๆแล้ว ณ จุดนี้ที่เก็บมีอยู่เพื่อสรุปเลเยอร์ฐานข้อมูลของฉัน
ตัวอย่างของฉันฉันจะสร้างวัตถุแบบสอบถามเพื่อค้นหา "AllUsers" นี่คืออินเทอร์เฟซ:
interface AllUsersQueryInterface
{
public function fetch($fields);
}
การใช้งานแบบสอบถามวัตถุ
นี่คือที่เราสามารถใช้ data mapper อีกครั้งเพื่อช่วยเร่งการพัฒนา โปรดสังเกตว่าฉันอนุญาตให้ปรับแต่งหนึ่งชุดข้อมูลที่ส่งคืน - เขตข้อมูล นี่จะเกี่ยวกับเท่าที่ฉันต้องการไปกับการจัดการแบบสอบถามที่ดำเนินการ โปรดจำไว้ว่าวัตถุแบบสอบถามของฉันไม่ได้เป็นผู้สร้างแบบสอบถาม พวกเขาทำแบบสอบถามเฉพาะ อย่างไรก็ตามเนื่องจากฉันรู้ว่าฉันอาจใช้สิ่งนี้มากในหลาย ๆ สถานการณ์ฉันจึงให้ความสามารถในการระบุฟิลด์ ฉันไม่ต้องการคืนช่องที่ฉันไม่ต้องการ!
class AllUsersQuery implements AllUsersQueryInterface
{
protected $db;
public function __construct(Database $db)
{
$this->db = $db;
}
public function fetch($fields)
{
return $this->db->select($fields)->from('users')->orderBy('last_name, first_name')->rows();
}
}
ก่อนที่จะไปยังคอนโทรลเลอร์ฉันต้องการแสดงอีกตัวอย่างหนึ่งเพื่อแสดงให้เห็นว่ามันมีประสิทธิภาพเพียงใด AllOverdueAccounts
บางทีฉันอาจจะมีเครื่องมือการรายงานและความจำเป็นในการสร้างรายงานสำหรับ นี่อาจเป็นเรื่องยุ่งยากกับผู้ทำแผนที่ข้อมูลของฉันและฉันอาจต้องการเขียนจริงบางอย่างSQL
ในสถานการณ์นี้ ไม่มีปัญหานี่คือสิ่งที่วัตถุแบบสอบถามนี้อาจมีลักษณะ:
class AllOverdueAccountsQuery implements AllOverdueAccountsQueryInterface
{
protected $db;
public function __construct(Database $db)
{
$this->db = $db;
}
public function fetch()
{
return $this->db->query($this->sql())->rows();
}
public function sql()
{
return "SELECT...";
}
}
นี่เป็นการเก็บตรรกะทั้งหมดของฉันไว้สำหรับรายงานนี้ในชั้นเดียวและทดสอบได้ง่าย ฉันสามารถเยาะเย้ยเนื้อหาของหัวใจของฉันหรือแม้กระทั่งการใช้งานที่แตกต่างกันโดยสิ้นเชิง
ผู้ควบคุม
ตอนนี้ความสนุกสนานนำชิ้นส่วนทั้งหมดมารวมกัน โปรดทราบว่าฉันกำลังใช้การฉีดพึ่งพา โดยทั่วไปแล้วการพึ่งพาจะถูกฉีดเข้าไปในตัวสร้าง แต่จริง ๆ แล้วฉันชอบที่จะฉีดพวกเขาลงในวิธีการควบคุมของฉัน (เส้นทาง) สิ่งนี้จะลดกราฟวัตถุของคอนโทรลเลอร์และฉันคิดว่ามันอ่านง่ายขึ้น หมายเหตุถ้าคุณไม่ชอบวิธีนี้เพียงใช้วิธีการสร้างแบบดั้งเดิม
class UsersController
{
public function index(AllUsersQueryInterface $query)
{
// Fetch user data
$users = $query->fetch(['first_name', 'last_name', 'email']);
// Return view
return Response::view('all_users.php', ['users' => $users]);
}
public function add()
{
return Response::view('add_user.php');
}
public function insert(UserRepositoryInterface $repository)
{
// Create new user model
$user = new User;
$user->first_name = $_POST['first_name'];
$user->last_name = $_POST['last_name'];
$user->gender = $_POST['gender'];
$user->email = $_POST['email'];
// Save the new user
$repository->save($user);
// Return the id
return Response::json(['id' => $user->id]);
}
public function view(SpecificUserQueryInterface $query, $id)
{
// Load user data
if (!$user = $query->fetch($id, ['first_name', 'last_name', 'gender', 'email'])) {
return Response::notFound();
}
// Return view
return Response::view('view_user.php', ['user' => $user]);
}
public function edit(SpecificUserQueryInterface $query, $id)
{
// Load user data
if (!$user = $query->fetch($id, ['first_name', 'last_name', 'gender', 'email'])) {
return Response::notFound();
}
// Return view
return Response::view('edit_user.php', ['user' => $user]);
}
public function update(UserRepositoryInterface $repository)
{
// Load user model
if (!$user = $repository->find($id)) {
return Response::notFound();
}
// Update the user
$user->first_name = $_POST['first_name'];
$user->last_name = $_POST['last_name'];
$user->gender = $_POST['gender'];
$user->email = $_POST['email'];
// Save the user
$repository->save($user);
// Return success
return true;
}
public function delete(UserRepositoryInterface $repository)
{
// Load user model
if (!$user = $repository->find($id)) {
return Response::notFound();
}
// Delete the user
$repository->delete($user);
// Return success
return true;
}
}
ความคิดสุดท้าย:
สิ่งสำคัญที่ควรทราบที่นี่คือเมื่อฉันแก้ไขเอนทิตี (สร้างอัปเดตหรือลบ) ฉันกำลังทำงานกับวัตถุโมเดลจริงและดำเนินการเก็บข้อมูลผ่านที่เก็บของฉัน
อย่างไรก็ตามเมื่อฉันแสดง (การเลือกข้อมูลและส่งไปยังมุมมอง) ฉันไม่ได้ทำงานกับวัตถุแบบจำลอง แต่เป็นวัตถุค่าเก่าแบบธรรมดา ฉันเลือกเฉพาะเขตข้อมูลที่ฉันต้องการและมันถูกออกแบบมาเพื่อให้ฉันสามารถเพิ่มประสิทธิภาพการค้นหาข้อมูลได้สูงสุด
ที่เก็บของฉันยังคงสะอาดอยู่และ "ระเบียบ" นี้จะถูกจัดเป็นแบบสอบถามแบบของฉัน
ฉันใช้ data mapper เพื่อช่วยในการพัฒนาเนื่องจากมันไร้สาระแค่เขียน SQL ซ้ำสำหรับงานทั่วไป อย่างไรก็ตามคุณสามารถเขียน SQL ได้อย่างที่ต้องการ (แบบสอบถามที่ซับซ้อนการรายงานและอื่น ๆ ) และเมื่อคุณทำเช่นนั้นมันซ่อนตัวอยู่ในชั้นเรียนที่มีชื่อเหมาะสม
ฉันชอบที่จะได้ยินเสียงของคุณในแนวทางของฉัน!
อัปเดตกรกฎาคม 2558:
ฉันถูกถามในความคิดเห็นที่ฉันจบลงด้วยทั้งหมดนี้ ไม่จริงที่ไกล จริงๆแล้วฉันยังไม่ชอบที่เก็บของจริงๆ ฉันพบว่าพวกเขา overkill สำหรับการค้นหาขั้นพื้นฐาน (โดยเฉพาะถ้าคุณใช้ ORM อยู่แล้ว) และยุ่งเมื่อทำงานกับคำค้นหาที่ซับซ้อนมากขึ้น
ฉันมักจะทำงานกับ ORM สไตล์ของ ActiveRecord ดังนั้นส่วนใหญ่ฉันมักจะอ้างอิงโมเดลเหล่านั้นโดยตรงผ่านแอปพลิเคชันของฉัน อย่างไรก็ตามในสถานการณ์ที่ฉันมีคิวรีที่ซับซ้อนมากขึ้นฉันจะใช้ออบเจ็กต์คิวรีเพื่อทำให้สามารถใช้ซ้ำได้ ฉันควรทราบด้วยว่าฉันมักจะฉีดแบบจำลองของฉันลงในวิธีการของฉันทำให้ง่ายต่อการเยาะเย้ยในการทดสอบของฉัน