अस्वीकरण: निम्नलिखित विवरण है कि मैं PHP आधारित वेब अनुप्रयोगों के संदर्भ में एमवीसी जैसे पैटर्न को कैसे समझता हूं। सभी बाहरी लिंक जो सामग्री में उपयोग किए जाते हैं, वे शब्दों और अवधारणाओं को समझाने के लिए हैं, न कि इस विषय पर मेरी अपनी विश्वसनीयता का अनुमान लगाने के लिए।
पहली बात जो मुझे स्पष्ट करनी चाहिए वह है: मॉडल एक परत है ।
दूसरा: शास्त्रीय एमवीसी और वेब विकास में हम क्या उपयोग करते हैं, के बीच अंतर है । यहाँ मैंने एक पुराना उत्तर लिखा है, जो संक्षेप में बताता है कि वे कैसे भिन्न हैं।
एक मॉडल क्या नहीं है:
मॉडल एक वर्ग या कोई एकल वस्तु नहीं है। यह एक बहुत ही सामान्य गलती है (मैंने भी किया था, हालांकि मूल उत्तर तब लिखा गया था जब मैंने अन्यथा सीखना शुरू किया था) , क्योंकि अधिकांश रूपरेखा इस गलत धारणा को बनाए रखती हैं।
न तो यह ऑब्जेक्ट-रिलेशनल मैपिंग तकनीक (ओआरएम) है और न ही डेटाबेस टेबलों का अमूर्तन है। जो कोई भी आपको अन्यथा बताता है, वह सबसे अधिक संभावना है कि वह किसी अन्य ब्रांड-ओआरएम या पूरे ढांचे को 'बेचने' की कोशिश कर रहा है ।
एक मॉडल क्या है:
उचित MVC अनुकूलन में, एम सभी डोमेन व्यापार तर्क होता है और मॉडल परत है ज्यादातर संरचनाओं के तीन प्रकार से बनाया गया:
डोमेन ऑब्जेक्ट
एक डोमेन ऑब्जेक्ट विशुद्ध रूप से डोमेन जानकारी का एक तार्किक कंटेनर है; यह आमतौर पर समस्या क्षेत्र में तार्किक इकाई का प्रतिनिधित्व करता है। आमतौर पर व्यावसायिक तर्क के रूप में संदर्भित किया जाता है ।
यह वह जगह होगी जहां आप चालान भेजने से पहले डेटा को मान्य करने या किसी ऑर्डर की कुल लागत की गणना करने के तरीके को परिभाषित करेंगे। उसी समय, डोमेन ऑब्जेक्ट्स स्टोरेज से पूरी तरह से अनजान हैं - न तो जहां से (एसक्यूएल डेटाबेस, रीस्ट एपीआई, टेक्स्ट फाइल, आदि) और न ही यहां तक कि अगर वे सहेजे गए या पुनर्प्राप्त किए जाते हैं।
डेटा मैपर
ये ऑब्जेक्ट केवल संग्रहण के लिए जिम्मेदार हैं। यदि आप किसी डेटाबेस में जानकारी संग्रहीत करते हैं, तो यह वह जगह होगी जहां SQL रहता है। या हो सकता है कि आप डेटा स्टोर करने के लिए एक XML फ़ाइल का उपयोग करें, और आपका डेटा मैपर XML फ़ाइलों से और उससे पार्स कर रहा हो।
सेवाएं
आप उन्हें "उच्च स्तरीय डोमेन ऑब्जेक्ट्स" के रूप में सोच सकते हैं, लेकिन व्यावसायिक तर्क के बजाय, डोमेन ऑब्जेक्ट्स और मैपर्स के बीच बातचीत के लिए सेवाएँ जिम्मेदार हैं । ये संरचनाएं डोमेन व्यवसाय तर्क के साथ बातचीत के लिए एक "सार्वजनिक" इंटरफ़ेस बनाती हैं। आप उनसे बच सकते हैं, लेकिन कुछ डोमेन लॉजिक को कंट्रोलर्स में लीक करने के दंड पर ।
ACL कार्यान्वयन प्रश्न में इस विषय से संबंधित उत्तर है - यह उपयोगी हो सकता है।
मॉडल लेयर और MVC ट्रायड के अन्य भागों के बीच संचार सेवा के माध्यम से ही होना चाहिए । स्पष्ट पृथक्करण के कुछ अतिरिक्त लाभ हैं:
- यह एकल जिम्मेदारी सिद्धांत (एसआरपी) को लागू करने में मदद करता है
- तर्क में बदलाव होने पर अतिरिक्त। विग्लिंग रूम ’प्रदान करता है
- नियंत्रक को यथासंभव सरल रखता है
- एक स्पष्ट खाका देता है, अगर आपको कभी बाहरी एपीआई की आवश्यकता होती है
एक मॉडल के साथ बातचीत कैसे करें?
आवश्यक शर्तें: घड़ी व्याख्यान "ग्लोबल स्टेट एंड सिंग्लेट्सन" और "डोंट लुक फॉर थिंग्स!" स्वच्छ संहिता वार्ता से।
सेवा उदाहरणों के लिए पहुँच प्राप्त करना
इन सेवाओं तक पहुँचने के लिए व्यू और कंट्रोलर इंस्टेंस (जिसे आप कॉल कर सकते हैं: "UI लेयर") दोनों के लिए, दो सामान्य दृष्टिकोण हैं:
- आप अपने विचारों और नियंत्रकों के निर्माणकर्ताओं में आवश्यक सेवाओं को सीधे इंजेक्ट कर सकते हैं, अधिमानतः डि कंटेनर का उपयोग करके।
- अपने सभी विचारों और नियंत्रकों के लिए अनिवार्य निर्भरता के रूप में सेवाओं के लिए एक कारखाने का उपयोग करना।
जैसा कि आप संदेह कर सकते हैं, डि कंटेनर एक बहुत अधिक सुरुचिपूर्ण समाधान है (जबकि शुरुआत के लिए सबसे आसान नहीं है)। दो पुस्तकालयों, कि मैं इस कार्यक्षमता के लिए विचार करने की सलाह देता हूं Syfmony के स्टैंडअलोन निर्भरता Injection घटक या Auryn होगा ।
एक कारखाने और DI कंटेनर का उपयोग करने वाले दोनों समाधान आपको दिए गए अनुरोध-प्रतिक्रिया चक्र के लिए चयनित नियंत्रक और दृश्य के बीच साझा किए जाने वाले विभिन्न सर्वरों के उदाहरणों को साझा करने देंगे।
मॉडल के राज्य का परिवर्तन
अब जब आप नियंत्रकों में मॉडल परत तक पहुंच सकते हैं, तो आपको वास्तव में उनका उपयोग करना शुरू करना होगा:
public function postLogin(Request $request)
{
$email = $request->get('email');
$identity = $this->identification->findIdentityByEmailAddress($email);
$this->identification->loginWithPassword(
$identity,
$request->get('password')
);
}
आपके नियंत्रकों के पास एक बहुत ही स्पष्ट कार्य है: उपयोगकर्ता इनपुट लें और इस इनपुट के आधार पर, व्यापार तर्क की वर्तमान स्थिति को बदलें। इस उदाहरण में जिन राज्यों के बीच परिवर्तन किया गया है वे हैं "अनाम उपयोगकर्ता" और "लॉग इन उपयोगकर्ता"।
उपयोगकर्ता के इनपुट को मान्य करने के लिए नियंत्रक जिम्मेदार नहीं है, क्योंकि यह व्यावसायिक नियमों का हिस्सा है और नियंत्रक निश्चित रूप से एसक्यूएल प्रश्नों को कॉल नहीं कर रहा है, जैसे कि आप यहां या यहां क्या देखेंगे (कृपया उन पर नफरत न करें, वे गुमराह हैं, बुराई नहीं)।
उपयोगकर्ता को राज्य-परिवर्तन दिखा रहा है।
ठीक है, उपयोगकर्ता लॉग इन (या विफल) है। अब क्या? कहा उपयोगकर्ता अभी भी इससे अनजान है। इसलिए आपको वास्तव में प्रतिक्रिया देने की जरूरत है और यह एक दृश्य की जिम्मेदारी है।
public function postLogin()
{
$path = '/login';
if ($this->identification->isUserLoggedIn()) {
$path = '/dashboard';
}
return new RedirectResponse($path);
}
इस मामले में, दृश्य ने मॉडल परत की वर्तमान स्थिति के आधार पर दो संभावित प्रतिक्रियाओं में से एक का उत्पादन किया। एक अलग उपयोग-मामले के लिए आपको "लेख के वर्तमान चयनित" जैसे कुछ के आधार पर प्रस्तुत करने के लिए अलग-अलग टेम्पलेट चुनने का दृश्य होगा।
प्रस्तुति परत वास्तव में काफी विस्तृत हो सकती है, जैसा कि यहां वर्णित है: PHP में एमवीसी व्यूज़ को समझना ।
लेकिन मैं सिर्फ एक REST API बना रहा हूँ!
बेशक, ऐसी स्थितियां हैं, जब यह एक ओवरकिल है।
एमवीसी, सिर्फ़ सेपरेशन थ्योरी के अलगाव का एक ठोस समाधान है । MVC उपयोगकर्ता इंटरफ़ेस को व्यावसायिक तर्क से अलग करता है, और UI में इसे उपयोगकर्ता इनपुट और प्रस्तुति से अलग करता है। यह महत्वपूर्ण है। जबकि अक्सर लोग इसे "त्रय" के रूप में वर्णित करते हैं, यह वास्तव में तीन स्वतंत्र भागों से नहीं बना है। संरचना इस तरह से अधिक है:
इसका मतलब यह है कि, जब आपकी प्रस्तुति परत का तर्क किसी के पास नहीं है, तो व्यावहारिक दृष्टिकोण उन्हें एकल परत के रूप में रखना है। यह मॉडल परत के कुछ पहलुओं को भी काफी हद तक सरल कर सकता है।
इस दृष्टिकोण का उपयोग करके लॉगिन उदाहरण (एक एपीआई के लिए) के रूप में लिखा जा सकता है:
public function postLogin(Request $request)
{
$email = $request->get('email');
$data = [
'status' => 'ok',
];
try {
$identity = $this->identification->findIdentityByEmailAddress($email);
$token = $this->identification->loginWithPassword(
$identity,
$request->get('password')
);
} catch (FailedIdentification $exception) {
$data = [
'status' => 'error',
'message' => 'Login failed!',
]
}
return new JsonResponse($data);
}
हालांकि यह स्थायी नहीं है, जब आपके पास प्रतिक्रिया निकाय को प्रस्तुत करने के लिए जटिल तर्क होते हैं, तो यह सरलीकरण अधिक तुच्छ परिदृश्यों के लिए बहुत उपयोगी है। लेकिन चेतावनी दी जाए , यह दृष्टिकोण एक बुरा सपना बन जाएगा, जब जटिल प्रस्तुति तर्क के साथ बड़े कोडबेस में उपयोग करने का प्रयास किया जाएगा।
मॉडल का निर्माण कैसे करें?
चूंकि एक भी "मॉडल" वर्ग नहीं है (जैसा कि ऊपर बताया गया है), आप वास्तव में "मॉडल का निर्माण नहीं करते हैं"। इसके बजाय आप सेवाओं को बनाने से शुरू करते हैं, जो कुछ निश्चित तरीकों का प्रदर्शन करने में सक्षम हैं। और फिर डोमेन ऑब्जेक्ट्स और मैपर्स को लागू करें ।
सेवा पद्धति का एक उदाहरण:
ऊपर के दोनों तरीकों में पहचान सेवा के लिए यह लॉगिन विधि थी। यह वास्तव में कैसा दिखेगा। मैं लाइब्रेरी से उसी कार्यक्षमता का थोड़ा संशोधित संस्करण उपयोग कर रहा हूं , जो मैंने लिखा है .. क्योंकि मैं आलसी हूं:
public function loginWithPassword(Identity $identity, string $password): string
{
if ($identity->matchPassword($password) === false) {
$this->logWrongPasswordNotice($identity, [
'email' => $identity->getEmailAddress(),
'key' => $password, // this is the wrong password
]);
throw new PasswordMismatch;
}
$identity->setPassword($password);
$this->updateIdentityOnUse($identity);
$cookie = $this->createCookieIdentity($identity);
$this->logger->info('login successful', [
'input' => [
'email' => $identity->getEmailAddress(),
],
'user' => [
'account' => $identity->getAccountId(),
'identity' => $identity->getId(),
],
]);
return $cookie->getToken();
}
जैसा कि आप देख सकते हैं, अमूर्तता के इस स्तर पर, इस बात का कोई संकेत नहीं है कि डेटा कहां से लाया गया था। यह एक डेटाबेस हो सकता है, लेकिन यह भी परीक्षण के प्रयोजनों के लिए सिर्फ एक नकली वस्तु हो सकता है। यहां तक कि डेटा मैपर्स, जो वास्तव में इसके लिए उपयोग किए जाते हैं, private
इस सेवा के तरीकों में दूर छिपे हुए हैं ।
private function changeIdentityStatus(Entity\Identity $identity, int $status)
{
$identity->setStatus($status);
$identity->setLastUsed(time());
$mapper = $this->mapperFactory->create(Mapper\Identity::class);
$mapper->store($identity);
}
मैपर बनाने के तरीके
दृढ़ता के अमूर्त को लागू करने के लिए, सबसे लचीले दृष्टिकोण पर कस्टम डेटा मैपर्स बनाना है ।
प्रेषक: पोइया पुस्तक
व्यवहार में उन्हें विशिष्ट वर्गों या सुपरक्लास के साथ बातचीत के लिए लागू किया जाता है। कहते हैं कि आपके पास Customer
और Admin
आपके कोड में (दोनों एक User
सुपरक्लास से विरासत में ) हैं। दोनों का अलग-अलग मिलान मैपर होगा, क्योंकि उनमें अलग-अलग क्षेत्र होते हैं। लेकिन आप साझा और आमतौर पर उपयोग किए जाने वाले कार्यों के साथ भी समाप्त हो जाएंगे। उदाहरण के लिए: "अंतिम बार ऑनलाइन देखा गया" समय अपडेट करना । और मौजूदा मैपर को अधिक जटिल बनाने के बजाय, अधिक व्यावहारिक दृष्टिकोण के लिए एक सामान्य "उपयोगकर्ता मैपर" होना चाहिए, जो केवल उस टाइमस्टैम्प को अपडेट करता है।
कुछ अतिरिक्त टिप्पणियां:
डेटाबेस टेबल और मॉडल
हालांकि कभी-कभी एक डेटाबेस टेबल, डोमेन ऑब्जेक्ट और मैपर के बीच 1: 1: 1 संबंध होता है , बड़ी परियोजनाओं में यह आपकी अपेक्षा से कम आम हो सकता है:
किसी एकल डोमेन ऑब्जेक्ट द्वारा उपयोग की जाने वाली जानकारी को विभिन्न तालिकाओं से मैप किया जा सकता है, जबकि ऑब्जेक्ट का डेटाबेस में कोई दृढ़ता नहीं है।
उदाहरण: यदि आप एक मासिक रिपोर्ट उत्पन्न कर रहे हैं। यह विभिन्न तालिकाओं से जानकारी एकत्र करेगा, लेकिन MonthlyReport
डेटाबेस में कोई जादुई तालिका नहीं है।
एक एकल मैपर कई तालिकाओं को प्रभावित कर सकता है।
उदाहरण: जब आप User
ऑब्जेक्ट से डेटा स्टोर कर रहे हैं , तो इस डोमेन ऑब्जेक्ट में अन्य डोमेन ऑब्जेक्ट - Group
इंस्टेंस के संग्रह हो सकते हैं । यदि आप उन्हें बदल देते हैं और डेटा संग्रहीत करते हैं User
, तो डेटा मैपर को कई तालिकाओं में प्रविष्टियों को अपडेट और / या सम्मिलित करना होगा।
किसी एकल डोमेन ऑब्जेक्ट का डेटा एक से अधिक तालिका में संग्रहीत किया जाता है।
उदाहरण: बड़ी प्रणालियों में (सोचें: एक मध्यम आकार का सामाजिक नेटवर्क), यह उपयोगकर्ता प्रमाणीकरण डेटा और अक्सर-एक्सेस किए गए डेटा को सामग्री के बड़े हिस्से से अलग करके संग्रहीत करने के लिए व्यावहारिक हो सकता है, जिसकी शायद ही कभी आवश्यकता होती है। उस स्थिति में आपके पास अभी भी एक एकल User
वर्ग हो सकता है , लेकिन इसमें मौजूद जानकारी इस बात पर निर्भर करेगी कि क्या पूरा विवरण प्राप्त किया गया था।
प्रत्येक डोमेन ऑब्जेक्ट के लिए एक से अधिक मैपर हो सकते हैं
उदाहरण: आपके पास सार्वजनिक-सामना और प्रबंधन सॉफ़्टवेयर दोनों के लिए साझा कोडबेड के साथ एक समाचार साइट है। लेकिन, जबकि दोनों इंटरफेस एक ही Article
वर्ग का उपयोग करते हैं , प्रबंधन को इसमें बहुत अधिक जानकारी की आवश्यकता होती है। इस मामले में आपके पास दो अलग-अलग मैपर्स होंगे: "आंतरिक" और "बाहरी"। प्रत्येक अलग-अलग प्रश्न करते हैं, या यहां तक कि अलग-अलग डेटाबेस (जैसे मास्टर या दास) का उपयोग करते हैं।
एक दृश्य एक टेम्पलेट नहीं है
एमवीसी में उदाहरण देखें (यदि आप पैटर्न के एमवीपी भिन्नता का उपयोग नहीं कर रहे हैं) प्रस्तुति तर्क के लिए जिम्मेदार हैं। इसका मतलब यह है कि प्रत्येक दृश्य आमतौर पर कम से कम कुछ टेम्पलेट्स को टटोलता है। यह मॉडल लेयर से डेटा प्राप्त करता है और फिर, प्राप्त जानकारी के आधार पर, एक टेम्पलेट चुनता है और मान सेट करता है।
इससे आपको जो लाभ होता है, उसमें से एक लाभ पुन: प्रयोज्य है। यदि आप एक ListView
वर्ग बनाते हैं , तो, अच्छी तरह से लिखे गए कोड के साथ, आप उसी वर्ग को एक लेख के नीचे उपयोगकर्ता-सूची और टिप्पणियों की प्रस्तुति सौंप सकते हैं। क्योंकि वे दोनों एक ही प्रस्तुति तर्क हैं। आप बस टेम्पलेट्स स्विच करें।
आप मूल PHP टेम्प्लेट का उपयोग कर सकते हैं या कुछ तृतीय-पक्ष टेंपलेटिंग इंजन का उपयोग कर सकते हैं । कुछ तृतीय-पक्ष लाइब्रेरी भी हो सकती हैं, जो व्यू इंस्टेंस को पूरी तरह से बदलने में सक्षम हैं ।
उत्तर के पुराने संस्करण के बारे में क्या?
एकमात्र बड़ा बदलाव यह है कि पुराने संस्करण में जिसे मॉडल कहा जाता है , वह वास्तव में एक सेवा है । बाकी "पुस्तकालय सादृश्य" बहुत अच्छी तरह से रखता है।
एकमात्र दोष जो मैं देखता हूं कि यह वास्तव में अजीब पुस्तकालय होगा, क्योंकि यह आपको पुस्तक से जानकारी लौटाएगा, लेकिन आपको पुस्तक को खुद को छूने नहीं देगा, क्योंकि अन्यथा अमूर्तता "लीक" करना शुरू कर देगी। मुझे अधिक उपयुक्त सादृश्य के बारे में सोचना पड़ सकता है।
दृश्य और नियंत्रक उदाहरणों के बीच क्या संबंध है ?
MVC संरचना दो परतों से बना है: ui और मॉडल। UI परत में मुख्य संरचनाएं दृश्य और नियंत्रक हैं।
जब आप MVC डिज़ाइन पैटर्न का उपयोग करने वाली वेबसाइटों के साथ काम कर रहे हैं, तो सबसे अच्छा तरीका यह है कि विचारों और नियंत्रकों के बीच 1: 1 संबंध हो। प्रत्येक दृश्य आपकी वेबसाइट में एक पूरे पृष्ठ का प्रतिनिधित्व करता है और इसमें उस विशेष दृश्य के लिए आने वाले सभी अनुरोधों को संभालने के लिए एक समर्पित नियंत्रक है।
उदाहरण के लिए, एक खुले लेख का प्रतिनिधित्व करने के लिए, आपके पास \Application\Controller\Document
और होगा \Application\View\Document
। इसमें UI परत के लिए सभी मुख्य कार्यक्षमता शामिल होगी, जब यह लेखों से निपटने की बात आती है (निश्चित रूप से आपके पास कुछ XHR घटक हो सकते हैं जो सीधे लेख से संबंधित नहीं हैं) ।