लेख का लेखन प्रश्नों के साथ
habrahabr.ru/qa/34735 और
habrahabr.ru/qa/32135 से प्रेरित था, जिसके लिए मुझे उत्तर के रूप में पूर्ण और विस्तृत जानकारी नहीं मिल पाई, जिसमें बहुत कमी थी। मुझे उम्मीद है कि यह दूसरों के लिए उपयोगी होगा।
परियोजना, जिसका हिस्सा मुख्य ढांचे के रूप में जेडएफ के रूप में गिर गया, मोबाइल सेवा के लिए मोबाइल संस्करण था (कुछ बारीकियों के साथ अनुकूली डिजाइन) + एपीआई।
सामूहिक रूप से, एक आईपीए बनाने के लिए एक राजनीतिक और तकनीकी निर्णय लिया गया था, जिसके माध्यम से साइट और एप्लिकेशन दोनों संचार करेंगे।
इस पर, मुझे लगता है, प्रस्तावना पूरी हो सकती है और सबसे दिलचस्प हो सकती है।
सुविधा के लिए, मैंने लेख को 2 भागों में विभाजित किया।
पहले भाग में कुछ सिद्धांत, विचार और विभिन्न स्रोतों के संदर्भ शामिल हैं।
दूसरे भाग में, मैंने विस्तार से (कोड उदाहरणों के साथ) यह दिखाने की कोशिश की कि मैंने वास्तुकला के अपने संस्करण को कैसे लागू किया।
सिद्धांत की बिट
शुरुआत
प्रवेश सीमा के संदर्भ में Zend फ्रेमवर्क सबसे आसान नहीं है। मैंने उनकी विचारधारा में तल्लीन करने के लिए पर्याप्त समय बिताया, लेकिन उसके बाद, हर अगले कदम की उम्मीद, पूर्वानुमान और तार्किक है।
पर्याप्त मात्रा में
आधिकारिक दस्तावेज होने के बावजूद, कुछ स्थानों पर यह सिकुड़ा हुआ है (सहकर्मियों ने भी इसे "टवी-स्टाइल प्रलेखन" नाम दिया है), और स्रोत कोड का अध्ययन करने से बहुत कुछ लिया जाना है।
मैं तुरंत उन लोगों का ध्यान आकर्षित करना चाहता हूं जो इस चमत्कार की अखंडता के बारे में बात करते हैं - हाँ, ज़ेंड एक बड़ी, बड़े पैमाने पर बंदूक है, जिसमें से पहली नज़र में ... गौरैयों के साथ कोई लेना-देना नहीं है ... लेकिन इसे देखने के बाद और इसकी विशेषताओं का अध्ययन भी सतही रूप से कर सकता हूं। इस बंदूक का कैलिबर बहुत विन्यास योग्य है। एक बहुत अच्छा काम करने वाला ऑटोलैडर है जो आपको कक्षाओं के न्यूनतम सेट को जोड़ने की अनुमति देता है।
परीक्षण एप्लिकेशन (त्वरित शुरुआत) के ढांचे को इकट्ठा करने के बाद, उन्होंने ZF पर विकास की संभावनाओं, सिफारिशों और सर्वोत्तम अभ्यास के एक सक्रिय अध्ययन के साथ वास्तुकला को डिजाइन करने की प्रक्रिया शुरू की (मुझे वास्तव में
प्रस्तुति पसंद आई, इससे बहुत कुछ सीखा, पाठ में अभी भी इसके लिंक होंगे)।
MVC में मॉडल
कई लोग डेटाबेस स्तर पर डेटा तक पहुंचने के तरीके के रूप में मॉडल को देखते हैं और उसका वर्णन करते हैं, लेकिन यह पूरी तरह सच नहीं है। एक मॉडल एक संबंधपरक डेटाबेस या तालिका भी नहीं है। डेटा विभिन्न स्रोतों से आ सकता है।
मैंने मॉडल को एक बहु-स्तरीय परत के रूप में माना और अपने लिए 3 परतों का चयन किया:
- डोमेन मॉडल
- डेटा मैपर
- डेटा एक्सेस लेयर (DAL)
डोमेन मॉडल - ऑब्जेक्ट मेटाडेटा का विवरण, जिसमें गेटर्स, सेटर, डेटा सत्यापन और ऑब्जेक्ट व्यवहार (व्यवहार) का विवरण शामिल है।
एक राय है कि DomainEvents परत में व्यवहार का विवरण भी निकाला जा सकता है, और यह
टेबल डेटा गेटवे पैटर्न के अलावा कुछ और है।
मेरे कार्यान्वयन में यह स्तर डेटा भंडारण के तरीकों (और स्थानों) के बारे में कुछ नहीं जानता है।
डेटा मैपर एक ऐसी परत है जिसे सार वर्णन के स्तर से सीधे निम्न स्तर तक डेटा स्थानांतरित करने के लिए डिज़ाइन किया गया है।
DAL में डेटा संग्रहण स्रोत के प्रत्यक्ष अनुरोध शामिल हैं। वहां आप SQL कोड और जीवन की अन्य सुविधाएं पा सकते हैं। ZF में, इस स्तर की भूमिका Zend_Db_Table और उसके डेरिवेटिव द्वारा निभाई जाती है।
यदि आप बाहरी ओआरएम का उपयोग करते हैं, जैसे कि डॉक्ट्रिन, तो यह अंतिम स्तरों को पूरी तरह से बदल देता है और डेवलपर के लिए जीवन को आसान बनाता है। चूंकि मैंने खुद को "विसर्जन के साथ सीखने" का लक्ष्य निर्धारित किया था, इसलिए मैंने तीसरे पक्ष के ओआरएम का उपयोग नहीं किया और
अपनी बाइक को अपने स्वयं के कार्यान्वयन का निर्णय लिया।
HowTo
परियोजना की संरचना
वास्तविक तस्वीर फ़ाइल संरचना के निम्नलिखित संगठन से मेल खाती है:
application/ controllers/ IndexController.php FooController.php models/ Abstract/ AbstractMapper.php AbstractModel.php DBTable/ FooTable.php DeviceTable.php Mapper/ FooMapper.php DeviceMapper.php Foo.php Device.php services/ DeviceService.php FooService.php views/
कुछ कोड उदाहरणों में
मैं एक पतली नियंत्रक लागू होने पर दृष्टिकोण का समर्थक हूं, और पूरे व्यवसाय को सेवाओं और मॉडलों में डाल दिया गया है। यह दृष्टिकोण कोड पुनरावृत्ति को कम करता है, परीक्षण को सरल करता है और तर्क में परिवर्तन करता है।
मैं एक "तटस्थ और मानक" नियंत्रक का एक उदाहरण दूंगा, जो इन प्रक्रियाओं से संबंधित प्राधिकरण, पंजीकरण और कार्यों के लिए जिम्मेदार है।
नियंत्रक उदाहरण class DeviceapiController extends Zend_Controller_Action { public function init() { $this->_helper->viewRenderer->setNoRender(true); } public function loginAction() { $request = $this->getRequest(); $data = $request->getRawBody(); if ($data) {
इस प्रकार, इस उदाहरण में नियंत्रक इनपुट डेटा की प्रारंभिक सत्यापनकर्ता की भूमिका निभाता है, व्यापार के लिए एक राउटर (कुछ सेवाओं को कॉल करना) और प्रतिक्रियाएं उत्पन्न करता है। मेरे उदाहरण में, मुझे केवल एपीआई के माध्यम से डेटा वापस करने की आवश्यकता थी। अधिक जटिल मामलों में, जब आपको एक ही तर्क पर काम करने की आवश्यकता होती है, केवल एक अलग प्रारूप में जवाब देने के लिए अनुरोध या अन्य मापदंडों के प्रकार पर निर्भर करता है, तो सामग्री स्विचर का उपयोग करना सुविधाजनक होता है। उदाहरण के लिए, यह उपयोगी हो सकता है जब साइट के साथ साधारण बातचीत के लिए एक ही अनुरोध का उपयोग किया जाता है, अजाक्स कॉल को संसाधित करने के लिए, या जब आपको एक ही डेटा को विभिन्न स्वरूपों में भेजने की आवश्यकता होती है (उदाहरण के लिए JSON या XML में, उदाहरण के लिए) सामग्री-प्रकार के अनुरोध से।
मेरी राय में, यह नियंत्रकों का सबसे कुशल उपयोग है, जो आपको आसानी से कार्यक्षमता का विस्तार करने की अनुमति देता है।
ऐसे नियंत्रक परीक्षण के लिए काफी आसान हैं। उसी समय, परीक्षण वास्तव में यह समझने में मदद करते हैं कि कार्यात्मक कैसे काम करता है, यह कैसे काम करना चाहिए। विकास प्रक्रिया के दौरान, मैंने टीडीडी के रूप में ऐसी तकनीकों को लागू नहीं किया था, इसलिए मैंने तैयार नियंत्रकों पर परीक्षण लिखा। इसने कुछ बाधाओं और संभावित बगों की पहचान करने में मदद की।
ऐसे नियंत्रकों की आसान परीक्षण क्षमता के बारे में मेरे शब्दों के समर्थन में, मैं नीचे दिए गए परीक्षणों का एक उदाहरण दूंगा।
इस तरह के नियंत्रक के लिए परीक्षण इस तरह दिखते हैं class LoginControllerTest extends Zend_Test_PHPUnit_ControllerTestCase { public function setUp() { $this->bootstrap = new Zend_Application(APPLICATION_ENV, APPLICATION_PATH . '/configs/application.ini'); parent::setUp(); } public function testSuccessfulLoginAction() { $request = $this->getRequest(); $email = 'email@example.com'; $request-> setMethod('POST')-> setHeader('Content-Type', 'application/json')-> setRawBody(Zend_Json::encode(array( 'email' => $email, 'password' => 'password', ))); $this->dispatch('/login'); $this->assertResponseCode(200); $this->assertNotRedirect(); $this->assertHeaderContains('Content-Type', 'application/json'); $data = $this->getResponse()->getBody(); $data = Zend_Json::decode($data, true); $this->assertArrayHasKey('secretKey', $data); $this->resetRequest() ->resetResponse();
सेवाओं ने व्यवसाय को ही लागू किया। मैंने सेवाओं के तरीकों को स्थिर बनाने की कोशिश की। इस दृष्टिकोण ने मुझे एक बार फिर सेवा ऑब्जेक्ट बनाने और संदर्भ और एक दूसरे पर सेवाओं की निर्भरता को कम करने की अनुमति नहीं दी, जिससे उनके परीक्षण, रिफ्लेक्टर, परिवर्तन करने, कार्यक्षमता का विस्तार करने में भी सुविधा होती है।
यह भी ध्यान देने योग्य है कि सेवाएं एक संदर्भ-स्वतंत्र प्रारूप में डेटा लौटाती हैं (उदाहरण के लिए, सरणियाँ), और नियंत्रक पहले से ही उन्हें एक विशिष्ट प्रारूप में पैक करने में लगे हुए हैं। इसलिए, यदि कल हमें डेटा ट्रांसफर के प्रारूप को बदलने की आवश्यकता होती है, तो हम इसे "अतिरिक्त रक्त के बिना"
हाथ की हल्की गति से बदल सकते हैं।
सेवा उदाहरण class Application_Service_DeviceService { public static function login (array $params) { if (!empty($params) && !empty($params['email']) && !empty($params['password'])) { $user = new Application_Model_User(); $device = new Application_Model_Device(); $adapter = new Zend_Auth_Adapter_DbTable( Zend_Controller_Front::getInstance()->getParam('bootstrap')->getPluginResource("db")->getDbAdapter(), 'user', 'email', 'password', 'MD5(CONCAT(?, passwordSalt,"'
जैसा कि आप कोड से देख सकते हैं, सेवा में, यदि आवश्यक हो, डेटा सत्यापन का अगला स्तर बाहर किया जाता है, मॉडल ऑब्जेक्ट बनाए जाते हैं, उनके गुणों और विधियों के साथ काम चल रहा है।
अगला, स्वयं उस मॉडल का एक उदाहरण पर विचार करें, जो हमारी वस्तुओं और उनके व्यवहार का वर्णन करेगा।
मॉडल वर्ग का उदाहरण class Application_Model_Device extends Application_Model_Abstract_AbstractModel { const ATTRIBUTE_ID = "id"; const ATTRIBUTE_USER_ID = "userId"; const ATTRIBUTE_SECRET_KEY = "secretKey"; const ATTRIBUTE_LAST_USAGE = "lastUsage"; protected $_id, $_userId, $_secretKey, $_lastUsage; public function __construct(array $options = null, $mapper = null) {
एक मॉडल विधि का एक और अधिक जटिल उदाहरण public function deleteFile() { $id = $this->id; if (empty($id)) { throw new Exception('Invalid id'); return false; } $imageFile = UPLOAD_PATH.'/'.$this->{self::ATTRIBUTE_REAL_NAME}; $thImageFile = THUMB_PATH.'/'.$this->{self::ATTRIBUTE_TH_NAME};
इस प्रकार, हमारे प्रत्यक्ष मॉडल में मेटाडेटा (ऑब्जेक्ट गुण) की परिभाषा शामिल है और उनके व्यवहार का वर्णन करता है। उसी समय, ऑब्जेक्ट का व्यवहार एक अमूर्त स्तर पर वर्णित होता है और मैपर से एक विशिष्ट विधि के लिए कॉल के साथ समाप्त होता है, जो पहले से ही रिपॉजिटरी के साथ बातचीत के लिए जिम्मेदार है। यदि आपको एक अतिरिक्त डेटा स्रोत कनेक्ट करने की आवश्यकता है, उदाहरण के लिए, कल हम एक अतिरिक्त NoSQL डेटाबेस का उपयोग करने का निर्णय लेते हैं, या कैश का उपयोग करना शुरू करते हैं, तो यह हमारे लिए सजाने के लिए पर्याप्त होगा। एक बार फिर मैं
प्रस्तुति का उल्लेख करना चाहता हूं, जहां इस दृष्टिकोण के सभी फायदे बहुत स्पष्ट रूप से प्रदर्शित किए गए हैं।
गहरा गोता लगाना।
मेरे कार्यान्वयन में अगला स्तर मैपर है। इसका मुख्य उद्देश्य डेटा या मॉडल से डीएएल स्तर तक एक क्वेरी को अग्रेषित करना है। दूसरे शब्दों में, इस स्तर पर हम टेबल डेटा गेटवे पैटर्न को लागू कर रहे हैं।
मैपर उदाहरण class Application_Model_DeviceMapper extends Application_Model_Abstract_AbstractMapper { const MODEL_TABLE = "Application_Model_DBTable_DeviceTable"; const MODEL_CLASS = "Application_Model_Device"; public function getDbTable() { if (null === $this->_dbTable) { $this->setDbTable(self::MODEL_TABLE); } return $this->_dbTable; } public function _getModel() { return new Application_Model_Device(); } public function update(array $data, $where) {
अपने कार्य के हिस्से के रूप में, मैंने केवल एक मैपर लागू किया - MySql डेटाबेस के साथ काम करना, लेकिन पहले से ही एक कार्य है और एक कैश के साथ काम करना जोड़ता है, और संभावित विचार कई वस्तुओं को NoSQL में स्थानांतरित करना है। मेरे लिए, इसका मतलब केवल कोड की न्यूनतम मात्रा को सजाने और लिखने की आवश्यकता होगी। आपको परीक्षणों को फिर से लिखना नहीं पड़ेगा (नए लोगों को लिखने के अलावा :))
जैसा कि आप कोड से देख सकते हैं, यह मैपर टेबल क्लास - डीएएल तक पहुंचता है।
इस परत के लिए, मैं कुछ नया नहीं ले आया और Zend द्वारा प्रदान की जाने वाली मानक कक्षाओं का उपयोग किया।
वर्ग स्वयं बहुत जटिल नहीं दिखता है:
डेटा एक्सेस क्लास (DAL स्तर) class Application_Model_DBTable_DeviceTable extends Zend_Db_Table_Abstract { protected $_name = 'deviceKey'; protected $_primary = 'id'; protected $_referenceMap = array( 'Token' => array( 'columns' => 'userId', 'refTableClass' => 'Application_Model_DBTable_UserTable', 'refColumns' => 'id', 'onDelete' => self::CASCADE, 'onUpdate' => self::CASCADE, )); public function __construct($config = array()) { $this->_setAdapter(Zend_Db_Table::getDefaultAdapter()); parent::__construct(); } }
यदि आप Zend फ्रेमवर्क मैनुअल को देखते हैं, तो यह नोटिस करना आसान है कि यह (और केवल यह स्तर) मॉडल के कार्यान्वयन के रूप में प्रस्तावित है (मैनुअल + क्विक स्टार्ट देखें)।
इसके अलावा, मैं मैपर और मॉडल के सार तरीकों का उपयोग करता हूं, लेकिन उनका उद्देश्य, मुझे आशा है, स्पष्ट है।
इसके अलावा, मैं यह कहना चाहता हूं कि Zend_Db_Table या तो सरणियों में मान देता है, या संबंधित प्रकार के ऑब्जेक्ट के रूप में, जो उस प्रकार के ऑब्जेक्ट के अनुरूप नहीं है जिसकी हमें आवश्यकता है, जिसके संदर्भ में हम इन विधियों को कहते हैं।
डेटा वेयरहाउस से प्राप्त डेटा को लाने के लिए, साथ ही उन्हें मान्य करने के लिए, हम मॉडल स्तर (ओआरओडी) पर निर्दिष्ट विधियों का उपयोग कर सकते हैं।
सारांश
पर्याप्त कोड वाले इस लेख के साथ, मैं इस दृष्टिकोण को हर किसी पर थोपने की कोशिश नहीं कर रहा हूं या यह कहूं कि मुझे ZF1 का उपयोग जारी रखने की जरूरत है, और एक उज्जवल भविष्य के लिए फ्रेमवर्क की दूसरी शाखा को छोड़ना होगा, जो अधिक आधुनिक है। हां, हम विकसित हो रहे हैं, बढ़ रहे हैं और विकसित हो रहे हैं, लेकिन सामान्य सिद्धांत, वास्तुशिल्प सहित, हमेशा इस्तेमाल किए गए उपकरण की परवाह किए बिना प्रासंगिक हैं।
पहली नज़र में, ऊपर वर्णित समाधान मैं मैनुअल में वर्णित एक से अधिक जटिल है। इसके अलावा, अनिवार्य रूप से, अधिक ऑब्जेक्ट बनाए जाते हैं और अंदर एक गहरा डेटा अग्रेषित होता है।
ये निस्संदेह विपक्ष हैं।
अब पेशेवरों के बारे में।
- हम एक अधिक परमाणु कोड प्राप्त करते हैं, जो परीक्षण करने के लिए अधिक सुविधाजनक है, जिसका अर्थ है कि इसे पढ़ना आसान होगा, बेहतर होगा और इसमें त्रुटियों की संभावना बहुत कम होगी।
- लचीलापन, एक्स्टेंसिबिलिटी। कार्यक्षमता का विस्तार करने के लिए आपको केवल मौजूदा कोड को सजाने की आवश्यकता है।
- स्तरों के बीच "जिम्मेदारी के क्षेत्रों" का पृथक्करण।
हम में से प्रत्येक, एक विशिष्ट परियोजना पर, जो वास्तुकला को लागू करने का फैसला करता है। मैंने अभी एक अन्य विकल्प का वर्णन किया है जो सफलतापूर्वक काम करता है और प्लसस की संख्या से कई मिनटों की संख्या निकल जाती है (मैं अपने और मेरे विशिष्ट प्रोजेक्ट के संदर्भ में कहता हूं)।
इसी तरह की रायमुझे उम्मीद है कि कोड उदाहरणों वाला यह लेख आपके लिए उपयोगी था।
आलोचना, सलाह और धन्यवाद के लिए भी मैं आभारी रहूंगा।
पुनश्च
मैं समझता हूं कि कोई सही कोड नहीं है और, लगभग हमेशा, आप बेहतर कर सकते हैं।
मैं यह भी समझता हूं कि आप तीसरे पक्ष के समाधान का उपयोग कर सकते हैं।
और हां, मैं समझता हूं कि ZF2 है, और इस पर नई परियोजनाएं करना शुरू करना बेहतर है।
मुझे यह भी एहसास है कि अन्य रूपरेखा / प्रोग्रामिंग भाषाएं हैं जिनमें कुछ चीजें तेजी से काम करती हैं / इष्टतम / उच्च / मजबूत / रूप सुंदर, आदि।