मुझे लगा कि मैं अपने प्रश्न का उत्तर देने में दरार डालूंगा। मेरे मूल प्रश्न 1-3 में मुद्दों को हल करने का सिर्फ एक तरीका निम्नलिखित है।
अस्वीकरण: मैं हमेशा पैटर्न या तकनीकों का वर्णन करते समय सही शब्दों का उपयोग नहीं कर सकता। उसके लिए खेद है।
लक्ष्य:
- देखने और संपादन के लिए एक बुनियादी नियंत्रक का एक पूरा उदाहरण बनाएं
Users
।
- सभी कोड पूरी तरह से परीक्षण योग्य और नकली होना चाहिए।
- नियंत्रक को पता नहीं होना चाहिए कि डेटा कहाँ संग्रहीत है (मतलब इसे बदला जा सकता है)।
- SQL कार्यान्वयन (सबसे सामान्य) दिखाने के लिए उदाहरण।
- अधिकतम प्रदर्शन के लिए, नियंत्रकों को केवल उन डेटा को प्राप्त करना चाहिए जिनकी उन्हें आवश्यकता है - कोई अतिरिक्त फ़ील्ड नहीं।
- विकास में आसानी के लिए कार्यान्वयन को कुछ प्रकार के डेटा मैपर का लाभ उठाना चाहिए।
- कार्यान्वयन में जटिल डेटा लुकअप करने की क्षमता होनी चाहिए।
समाधान
मैं अपने निरंतर भंडारण (डेटाबेस) इंटरैक्शन को दो श्रेणियों में विभाजित कर रहा हूं: आर (पढ़ें) और सीयूडी (क्रिएट, अपडेट, डिलीट)। मेरा अनुभव यह है कि वास्तव में पढ़ता है कि क्या एक अनुप्रयोग को धीमा करने का कारण बनता है। और जबकि डेटा हेरफेर (CUD) वास्तव में धीमा है, यह बहुत कम बार होता है, और इसलिए यह बहुत कम चिंता का विषय है।
CUD (क्रिएट, अपडेट, डिलीट) आसान है। इसमें वास्तविक मॉडलों के साथ काम करना शामिल होगा , जो तब Repositories
दृढ़ता के लिए मेरे पास भेजे जाते हैं। ध्यान दें, मेरी रिपॉजिटरी अभी भी एक रीड मेथड उपलब्ध कराएगी, लेकिन केवल ऑब्जेक्ट निर्माण के लिए, प्रदर्शन नहीं। उस पर और बाद में।
आर (पढ़ें) इतना आसान नहीं है। यहां कोई मॉडल नहीं, बस वस्तुओं को महत्व देते हैं । यदि आप चाहें तो सरणियों का उपयोग करें । ये ऑब्जेक्ट एकल मॉडल या कई मॉडल के मिश्रण का प्रतिनिधित्व कर सकते हैं, वास्तव में कुछ भी। ये अपने आप में बहुत दिलचस्प नहीं हैं, लेकिन वे कैसे उत्पन्न होते हैं। मैं जो बुला रहा हूं उसका उपयोग कर रहा हूं Query Objects
।
कोड:
उपयोगकर्ता मॉडल
आइए हम अपने मूल उपयोगकर्ता मॉडल के साथ सरल शुरुआत करें। ध्यान दें कि कोई भी ORM विस्तार या डेटाबेस सामान नहीं है। बस शुद्ध मॉडल महिमा। जो भी हो, अपना गेटअप, सेटर, वेलिडेशन जोड़ें।
class User
{
public $id;
public $first_name;
public $last_name;
public $gender;
public $email;
public $password;
}
रिपोजिटरी इंटरफ़ेस
अपना उपयोगकर्ता रिपॉजिटरी बनाने से पहले, मैं अपना रिपॉजिटरी इंटरफ़ेस बनाना चाहता हूं। यह "अनुबंध" को परिभाषित करेगा जो मेरे नियंत्रक द्वारा उपयोग किए जाने के लिए रिपॉजिटरी का पालन करना होगा। याद रखें, मेरे नियंत्रक को नहीं पता होगा कि डेटा वास्तव में कहाँ संग्रहीत है।
ध्यान दें कि मेरी रिपॉजिटरी में केवल ये तीन विधियाँ होंगी। save()
विधि दोनों बनाने और उपयोगकर्ताओं को अद्यतन करने के लिए, बस या नहीं, उपयोगकर्ता वस्तु एक आईडी सेट है पर निर्भर करता है के लिए जिम्मेदार है।
interface UserRepositoryInterface
{
public function find($id);
public function save(User $user);
public function remove(User $user);
}
एसक्यूएल रिपोजिटरी कार्यान्वयन
अब इंटरफ़ेस का मेरा कार्यान्वयन बनाने के लिए। जैसा कि उल्लेख किया गया है, मेरा उदाहरण एक SQL डेटाबेस के साथ होने वाला था। दोहराए जाने वाले 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 (क्रिएट, अपडेट, डिलीट) के साथ हमारे रिपॉजिटरी द्वारा ध्यान रखा गया है, हम आर (रीड) पर ध्यान केंद्रित कर सकते हैं । क्वेरी ऑब्जेक्ट केवल कुछ प्रकार के डेटा लुकअप लॉजिक का एक एनकैप्सुलेशन है। वे क्वेरी बिल्डरों नहीं हैं । इसे हमारे रिपॉजिटरी की तरह अमूर्त करके हम इसे लागू कर सकते हैं और इसे आसान बना सकते हैं। क्वेरी ऑब्जेक्ट का एक उदाहरण AllUsersQuery
या AllActiveUsersQuery
, या यहां तक कि हो सकता है MostCommonUserFirstNames
।
आप सोच रहे होंगे "क्या मैं उन प्रश्नों के लिए अपनी रिपॉजिटरी में सिर्फ तरीके नहीं बना सकता?" हां, लेकिन यहां मैं ऐसा क्यों नहीं कर रहा हूं:
- मेरे रिपॉजिटरी मॉडल ऑब्जेक्ट्स के साथ काम करने के लिए हैं। एक वास्तविक दुनिया ऐप में, मुझे
password
अपने सभी उपयोगकर्ताओं को सूचीबद्ध करने के लिए फ़ील्ड की आवश्यकता क्यों होगी ?
- रिपॉजिटरी अक्सर मॉडल विशिष्ट होते हैं, फिर भी प्रश्नों में अक्सर एक से अधिक मॉडल शामिल होते हैं। तो आप किस विधि को अपनाते हैं?
- यह मेरी रिपॉजिटरी को बहुत सरल रखता है - विधियों का एक फूला हुआ वर्ग नहीं।
- सभी प्रश्न अब अपनी कक्षाओं में आयोजित किए जाते हैं।
- वास्तव में, इस बिंदु पर, रिपॉजिटरी मेरे डेटाबेस लेयर को अमूर्त करने के लिए मौजूद हैं।
मेरे उदाहरण के लिए मैं "AllUsers" देखने के लिए एक क्वेरी ऑब्जेक्ट बनाऊंगा। यहाँ इंटरफ़ेस है:
interface AllUsersQueryInterface
{
public function fetch($fields);
}
क्वेरी ऑब्जेक्ट कार्यान्वयन
यह वह जगह है जहां हम विकास को गति देने में मदद करने के लिए फिर से डेटा मैपर का उपयोग कर सकते हैं। ध्यान दें कि मैं लौटे हुए डेटासेट - फ़ील्ड्स में से एक ट्वीक की अनुमति दे रहा हूं। यह तब तक के बारे में है जब तक मैं प्रदर्शन की गई क्वेरी में हेरफेर करना चाहता हूं। याद रखें, मेरी क्वेरी ऑब्जेक्ट क्वेरी बिल्डर्स नहीं हैं। वे बस एक विशिष्ट क्वेरी करते हैं। हालाँकि, जब से मुझे पता है कि मैं शायद इस एक का उपयोग कर रहा हूं, विभिन्न स्थितियों में, मैं खुद को खेतों को निर्दिष्ट करने की क्षमता दे रहा हूं। मैं उन क्षेत्रों को कभी वापस नहीं करना चाहता, जिनकी मुझे आवश्यकता नहीं है!
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;
}
}
अंतिम विचार:
यहां ध्यान देने योग्य महत्वपूर्ण बातें यह हैं कि जब मैं संस्थाओं को संशोधित (बनाना, अपडेट करना या हटाना) कर रहा हूं, तो मैं वास्तविक मॉडल ऑब्जेक्ट्स के साथ काम कर रहा हूं, और अपने रिपॉजिटरी के माध्यम से दृढ़ता का प्रदर्शन कर रहा हूं।
हालांकि, जब मैं प्रदर्शित कर रहा हूं (डेटा का चयन करना और उसे विचारों को भेजना) मैं मॉडल ऑब्जेक्ट्स के साथ काम नहीं कर रहा हूं, बल्कि सादे पुराने मूल्य की वस्तुओं के साथ काम कर रहा हूं। मैं केवल उन क्षेत्रों का चयन करता हूं जिनकी मुझे आवश्यकता है, और इसे डिज़ाइन किया गया है ताकि मैं अपने डेटा लुकअप प्रदर्शन को अधिकतम कर सकूं।
मेरे रिपॉजिटरी बहुत साफ रहते हैं, और इसके बजाय यह "गड़बड़" मेरे मॉडल प्रश्नों में व्यवस्थित है।
मैं विकास के साथ मदद करने के लिए एक डेटा मैपर का उपयोग करता हूं, क्योंकि यह सामान्य कार्यों के लिए दोहराए जाने वाले एसक्यूएल लिखने के लिए केवल हास्यास्पद है। हालाँकि, आप बिल्कुल SQL को जहाँ आवश्यक हो (जटिल प्रश्न, रिपोर्टिंग, आदि) लिख सकते हैं। और जब आप ऐसा करते हैं, तो यह अच्छी तरह से नामित कक्षा में भाग जाता है।
मैं अपने दृष्टिकोण पर अपने सुनने के लिए प्यार होता!
जुलाई 2015 अपडेट:
मुझे उन टिप्पणियों में पूछा गया है जहां मैं यह सब खत्म कर रहा हूं। ठीक है, वास्तव में दूर नहीं है। सच में, मुझे अभी भी रिपॉजिटरी पसंद नहीं है। मैं उन्हें मूल लुकअप (विशेषकर यदि आप पहले से ORM का उपयोग कर रहे हैं) के लिए ओवरकिल कर पाते हैं, और अधिक जटिल प्रश्नों के साथ काम करते समय गड़बड़ करते हैं।
मैं आम तौर पर एक ActiveRecord शैली ORM के साथ काम करता हूं, इसलिए सबसे अधिक बार मैं सिर्फ अपने आवेदन के दौरान सीधे उन मॉडलों का संदर्भ लूंगा। हालाँकि, ऐसी स्थितियों में जहाँ मेरे पास अधिक जटिल प्रश्न हैं, मैं इन अधिक पुन: प्रयोज्य बनाने के लिए क्वेरी ऑब्जेक्ट का उपयोग करूँगा। मुझे यह भी ध्यान देना चाहिए कि मैं हमेशा अपने मॉडल को अपने तरीकों में इंजेक्ट करता हूं, जिससे उन्हें मेरे परीक्षणों में मजाक करना आसान हो जाता है।