2009-07-03 26 views
13

में कोड की गुणवत्ता में सुधार करना मैं कुछ हफ्तों के लिए केकेपीएचपी का उपयोग कर रहा हूं और यह एक शानदार अनुभव रहा है। मैंने आश्चर्यजनक रूप से एक साइट को पोर्ट करने में कामयाब रहा है और मैंने नई सुविधाओं का एक समूह भी जोड़ा है जिसे मैंने योजना बनाई थी लेकिन इसे लागू करने के लिए कभी नहीं मिला।केकेपीएचपी

निम्नलिखित दो नियंत्रकों पर एक नज़र डालें, वे उपयोगकर्ता को अपने खाते से जुड़े साइटों में से किसी एक को प्रीमियम स्थिति जोड़ने की अनुमति देते हैं। वे बहुत 'केकी' महसूस नहीं करते हैं, क्या उन्हें किसी भी तरह से सुधार किया जा सकता है?

प्रीमियमसाइट नियंत्रक साइनअप प्रक्रिया को संभालता है और अंत में इतिहास से संबंधित अन्य संबंधित चीजें हैं।

class PremiumSitesController extends AppController { 

    var $name = 'PremiumSites'; 

    function index() { 
     $cost = 5; 

     //TODO: Add no site check 

     if (!empty($this->data)) { 
      if($this->data['PremiumSite']['type'] == "1") { 
       $length = (int) $this->data['PremiumSite']['length']; 
       $length++; 
       $this->data['PremiumSite']['upfront_weeks'] = $length; 
       $this->data['PremiumSite']['upfront_expiration'] = date('Y-m-d H:i:s', strtotime(sprintf('+%s weeks', $length))); 
       $this->data['PremiumSite']['cost'] = $cost * $length; 
      } else { 
       $this->data['PremiumSite']['cost'] = $cost; 
      } 

      $this->PremiumSite->create(); 
      if ($this->PremiumSite->save($this->data)) { 
       $this->redirect(array('controller' => 'paypal_notifications', 'action' => 'send', $this->PremiumSite->getLastInsertID())); 
      } else { 
       $this->Session->setFlash('Please fix the problems below', true, array('class' => 'error')); 
      } 
     } 

     $this->set('sites',$this->PremiumSite->Site->find('list',array('conditions' => array('User.id' => $this->Auth->user('id'), 'Site.is_deleted' => 0), 'recursive' => 0))); 
    } 

} 

PaypalNotifications नियंत्रक Paypal के साथ बातचीत को संभालती है।

class PaypalNotificationsController extends AppController { 

    var $name = 'PaypalNotifications'; 

    function beforeFilter() { 
     parent::beforeFilter(); 
     $this->Auth->allow('process'); 
    } 

    /** 
    * Compiles premium info and send the user to Paypal 
    * 
    * @param integer $premiumID an id from PremiumSite 
    * @return null 
    */ 
    function send($premiumID) { 

     if(empty($premiumID)) { 
      $this->Session->setFlash('There was a problem, please try again.', true, array('class' => 'error')); 
      $this->redirect(array('controller' => 'premium_sites', 'action' => 'index')); 
     } 

     $data = $this->PaypalNotification->PremiumSite->find('first', array('conditions' => array('PremiumSite.id' => $premiumID), 'recursive' => 0)); 

     if($data['PremiumSite']['type'] == '0') { 
      //Subscription 
      $paypalData = array(
       'cmd' => '_xclick-subscriptions', 
       'business'=> '', 
       'notify_url' => '', 
       'return' => '', 
       'cancel_return' => '', 
       'item_name' => '', 
       'item_number' => $premiumID, 
       'currency_code' => 'USD', 
       'no_note' => '1', 
       'no_shipping' => '1', 
       'a3' => $data['PremiumSite']['cost'], 
       'p3' => '1', 
       't3' => 'W', 
       'src' => '1', 
       'sra' => '1' 
      ); 

      if($data['Site']['is_premium_used'] == '0') { 
       //Apply two week trial if unused 
       $trialData = array(
        'a1' => '0', 
        'p1' => '2', 
        't1' => 'W', 
       ); 
       $paypalData = array_merge($paypalData, $trialData); 
      } 
     } else { 
      //Upfront payment 

      $paypalData = array(
       'cmd' => '_xclick', 
       'business'=> '', 
       'notify_url' => '', 
       'return' => '', 
       'cancel_return' => '', 
       'item_name' => '', 
       'item_number' => $premiumID, 
       'currency_code' => 'USD', 
       'no_note' => '1', 
       'no_shipping' => '1', 
       'amount' => $data['PremiumSite']['cost'], 
      ); 
     } 

     $this->layout = null; 
     $this->set('data', $paypalData); 
    } 

    /** 
    * IPN Callback from Paypal. Validates data, inserts it 
    * into the db and triggers __processTransaction() 
    * 
    * @return null 
    */ 
    function process() { 
     //Original code from http://www.studiocanaria.com/articles/paypal_ipn_controller_for_cakephp 
     //Have we been sent an IPN here... 
     if (!empty($_POST)) { 
      //...we have so add 'cmd' 'notify-validate' to a transaction variable 
      $transaction = 'cmd=_notify-validate'; 
      //and add everything paypal has sent to the transaction 
      foreach ($_POST as $key => $value) { 
       $value = urlencode(stripslashes($value)); 
       $transaction .= "&$key=$value"; 
      } 
      //create headers for post back 
      $header = "POST /cgi-bin/webscr HTTP/1.0\r\n"; 
      $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; 
      $header .= "Content-Length: " . strlen($transaction) . "\r\n\r\n"; 
      //If this is a sandbox transaction then 'test_ipn' will be set to '1' 
      if (isset($_POST['test_ipn'])) { 
       $server = 'www.sandbox.paypal.com'; 
      } else { 
       $server = 'www.paypal.com'; 
      } 
      //and post the transaction back for validation 
      $fp = fsockopen('ssl://' . $server, 443, $errno, $errstr, 30); 
      //Check we got a connection and response... 
      if (!$fp) { 
       //...didn't get a response so log error in error logs 
       $this->log('HTTP Error in PaypalNotifications::process while posting back to PayPal: Transaction=' . 
        $transaction); 
      } else { 
       //...got a response, so we'll through the response looking for VERIFIED or INVALID 
       fputs($fp, $header . $transaction); 
       while (!feof($fp)) { 
        $response = fgets($fp, 1024); 
        if (strcmp($response, "VERIFIED") == 0) { 
         //The response is VERIFIED so format the $_POST for processing 
         $notification = array(); 

         //Minor change to use item_id as premium_site_id 
         $notification['PaypalNotification'] = array_merge($_POST, array('premium_site_id' => $_POST['item_number'])); 
         $this->PaypalNotification->save($notification); 

         $this->__processTransaction($this->PaypalNotification->id); 
        } else 
         if (strcmp($response, "INVALID") == 0) { 
          //The response is INVALID so log it for investigation 
          $this->log('Found Invalid:' . $transaction); 
         } 
       } 
       fclose($fp); 
      } 
     } 
     //Redirect 
     $this->redirect('/'); 
    } 

    /** 
    * Enables premium site after payment 
    * 
    * @param integer $id uses id from PaypalNotification 
    * @return null 
    */ 
    function __processTransaction($id) { 
     $transaction = $this->PaypalNotification->find('first', array('conditions' => array('PaypalNotification.id' => $id), 'recursive' => 0)); 
     $txn_type = $transaction['PaypalNotification']['txn_type']; 

     if($txn_type == 'subscr_signup' || $transaction['PaypalNotification']['payment_status'] == 'Completed') { 
      //New subscription or payment 
      $data = array(
       'PremiumSite' => array(
        'id' => $transaction['PremiumSite']['id'], 
        'is_active' => '1', 
        'is_paid' => '1' 
       ), 
       'Site' => array(
        'id' => $transaction['PremiumSite']['site_id'], 
        'is_premium' => '1' 
       ) 
      ); 

      //Mark trial used only on subscriptions 
      if($txn_type == 'subscr_signup') $data['Site']['is_premium_used'] = '1'; 

      $this->PaypalNotification->PremiumSite->saveAll($data); 

     } elseif($txn_type == 'subscr-cancel' || $txn_type == 'subscr-eot') { 
      //Subscription cancellation or other problem 
      $data = array(
       'PremiumSite' => array(
        'id' => $transaction['PremiumSite']['id'], 
        'is_active' => '0', 
       ), 
       'Site' => array(
        'id' => $transaction['PremiumSite']['site_id'], 
        'is_premium' => '0' 
       ) 
      ); 

      $this->PaypalNotification->PremiumSite->saveAll($data); 
     } 


    } 

    /** 
    * Used for testing 
    * 
    * @return null 
    */ 
    function index() { 
     $this->__processTransaction('3'); 
    } 
} 

/views/paypal_notifications/send.ctp

सभी आवश्यक डेटा के साथ Paypal करने के लिए उपयोगकर्ता भेजता

echo "<html>\n"; 
echo "<head><title>Processing Payment...</title></head>\n"; 
echo "<body onLoad=\"document.form.submit();\">\n"; 
echo "<center><h3>Redirecting to paypal, please wait...</h3></center>\n"; 

echo $form->create(null, array('url' => 'https://www.sandbox.paypal.com/cgi-bin/webscr', 'type' => 'post', 'name' => 'form')); 

foreach ($data as $field => $value) { 
    //Using $form->hidden sends in the cake style, data[PremiumSite][whatever] 
    echo "<input type=\"hidden\" name=\"$field\" value=\"$value\">"; 
} 

echo $form->end(); 

echo "</form>\n"; 
echo "</body></html>\n"; 

+0

आपके लिए कोड स्वरूपण को फिक्स्ड – PatrikAkerstrand

+0

धन्यवाद, मैंने पहले यह कोशिश की, यह सुनिश्चित नहीं किया कि यह – DanCake

उत्तर

28

पाठ 1: PHP के superglobals का प्रयोग न करें

  • $_POST = $this->params['form'];
  • $_GET = $this->params['url'];
  • $_GLOBALS = Configure::write('App.category.variable', 'value');
  • $_SESSION (देखें) = $session->read(); (सहायक)
  • $_SESSION (नियंत्रक) = $this->Session->read(); (घटक)
  • $_SESSION['Auth']['User'] = $this->Auth->user();

$_POST के लिए प्रतिस्थापन:

<?php 
    ... 
    //foreach ($_POST as $key => $value) { 
    foreach ($this->params['form'] as $key => $value) { 
    ... 
    //if (isset($_POST['test_ipn'])) { 
    if (isset($this->params['form']['test_ipn'])) { 
    ... 
?> 

सबक 2: एक दृश्य करता है साझा करने के लिए (उपयोगकर्ता के साथ)

कोड "प्रीमियम जानकारी संकलित करता है और उपयोगकर्ता को पेपैल को भेजता है" दस्तावेज़ को उपयोगकर्ता को पेपैल में नहीं भेजता है। क्या आप दृश्य में रीडायरेक्ट कर रहे हैं?

<?php 
    function redirect($premiumId) { 
     ... 
     $this->redirect($url . '?' . http_build_query($paypalData), 303); 
    } 

अपने नियंत्रक के अंत में रीडायरेक्ट करें और दृश्य हटाएं। :)

पाठ 3: डेटा हेरफेर मॉडल परत में अंतर्गत आता है

<?php 
class PremiumSite extends AppModel { 
    ... 
    function beforeSave() { 
     if ($this->data['PremiumSite']['type'] == "1") { 
      $cost = Configure::read('App.costs.premium'); 
      $numberOfWeeks = ((int) $this->data['PremiumSite']['length']) + 1; 
      $timestring = String::insert('+:number weeks', array(
       'number' => $numberOfWeeks, 
      )); 
      $expiration = date('Y-m-d H:i:s', strtotime($timestring)); 
      $this->data['PremiumSite']['upfront_weeks'] = $weeks; 
      $this->data['PremiumSite']['upfront_expiration'] = $expiration; 
      $this->data['PremiumSite']['cost'] = $cost * $numberOfWeeks; 
     } else { 
      $this->data['PremiumSite']['cost'] = $cost; 
     } 
     return true; 
    } 
    ... 
} 
?> 

पाठ 4: मॉडल सिर्फ डेटाबेस का उपयोग के लिए नहीं हैं

ले जाएँ कोड प्रलेखित "भुगतान के बाद प्रीमियम साइट सक्षम बनाता है "प्रीमियमसाइट मॉडल पर, और भुगतान के बाद इसे कॉल करें:

<?php 
class PremiumSite extends AppModel { 
    ... 
    function enable($id) { 
     $transaction = $this->find('first', array(
      'conditions' => array('PaypalNotification.id' => $id), 
      'recursive' => 0, 
     )); 
     $transactionType = $transaction['PaypalNotification']['txn_type']; 

     if ($transactionType == 'subscr_signup' || 
      $transaction['PaypalNotification']['payment_status'] == 'Completed') { 
      //New subscription or payment 
      ... 
     } elseif ($transactionType == 'subscr-cancel' || 
      $transactionType == 'subscr-eot') { 
      //Subscription cancellation or other problem 
      ... 
     } 
     return $this->saveAll($data); 
    } 
    ... 
} 
?> 

आप $this->PaypalNotification->PremiumSite->enable(...); का उपयोग कर नियंत्रक से कहेंगे, लेकिन हम ऐसा करने के लिए नहीं जा रहे हैं, तो चलो यह सब एक साथ मिश्रण करते हैं ...

पाठ 5: datasources शांत

सार अपने पेपैल IPN एक में बातचीत कर रहे हैं डेटासोर्स जिसका उपयोग मॉडल द्वारा किया जाता है।

विन्यास app/config/database.php वेब सेवा अनुरोध के साथ

<?php 
class DATABASE_CONFIG { 
    ... 
    var $paypal = array(
     'datasource' => 'paypal_ipn', 
     'sandbox' => true, 
     'api_key' => 'w0u1dnty0ul1k3t0kn0w', 
    } 
    ... 
} 
?> 

डेटा स्रोत सौदों (app/models/datasources/paypal_ipn_source.php)

<?php 
class PaypalIpnSource extends DataSource { 
    ... 
    var $endpoint = 'http://www.paypal.com/'; 
    var $Http = null; 
    var $_baseConfig = array(
     'sandbox' => true, 
     'api_key' => null, 
    ); 

    function _construct() { 
     if (!$this->config['api_key']) { 
      trigger_error('No API key specified'); 
     } 
     if ($this->config['sandbox']) { 
      $this->endpoint = 'http://www.sandbox.paypal.com/'; 
     } 
     $this->Http = App::import('Core', 'HttpSocket'); // use HttpSocket utility lib 
    } 

    function validate($data) { 
     ... 
     $reponse = $this->Http->post($this->endpoint, $data); 
     .. 
     return $valid; // boolean 
    } 
    ... 
} 
?> 

चलो मॉडल में चला जाता है काम (app/models/paypal_notification.php)

सूचनाएं केवल तभी सहेजे जाते हैं कि वे मान्य हैं, साइट केवल तभी सक्षम की जाती है जब अधिसूचना सहेजी जाती है

<?php 
class PaypalNotification extends AppModel { 
    ... 
    function beforeSave() { 
     $valid = $this->validate($this->data); 
     if (!$valid) { 
      return false; 
     } 
     //Minor change to use item_id as premium_site_id 
     $this->data['PaypalNotification']['premium_site_id'] = 
      $this->data['PaypalNotification']['item_number']; 
     /* 
     $this->data['PaypalNotification'] = am($this->data, // use shorthand functions 
      array('premium_site_id' => $this->data['item_number'])); 
     */ 
     return true; 
    } 
    ... 
    function afterSave() { 
     return $this->PremiumSite->enable($this->id); 
    } 
    ... 
    function validate($data) { 
     $paypal = ConnectionManager::getDataSource('paypal'); 
     return $paypal->validate($data); 
    } 
    ... 
?> 

नियंत्रक गूंगा हैं। (app/controllers/paypal_notifications_controller.php)

"क्या आप एक पोस्ट हैं? नहीं? .. तो मैं भी अस्तित्व में नहीं हूं।" अब यह क्रिया सिर्फ चिल्लाती है, "मैं पोस्ट पेपैल सूचनाएं सहेजता हूं!"

<?php 
class PaypalNotificationsController extends AppModel { 
    ... 
    var $components = array('RequestHandler', ...); 
    ... 
    function callback() { 
     if (!$this->RequestHandler->isPost()) { // use RequestHandler component 
      $this->cakeError('error404'); 
     } 
     $processed = $this->PaypalNotification->save($notification); 
     if (!$processed) { 
      $this->cakeError('paypal_error'); 
     } 
    } 
    ... 
} 
?> 

बोनस दौर:

  • String बजाय sprintf
  • HttpSocket की बजाय fsock: उपयोग देशी पीएचपी

    पिछले अध्यायों के लिए निम्न के उदाहरण के लिए संदर्भ लें के बजाय पुस्तकालयों प्रदान की फ़ंक्शन

  • मैनुअल जांच
  • am बजाय array_merge

के बजाय RequestHandler इन त्रुटियों को कोडिंग रोका जा सकता है, कोड की मात्रा को कम और/या पठनीयता वृद्धि हुई है।

+0

के लिए धन्यवाद, सबसे पहले, उस उत्कृष्ट पोस्ट के लिए धन्यवाद, आपने मुझे इसके बारे में सोचने के लिए बहुत कुछ दिया है। लय 1: प्रक्रिया() अनुभाग मेरे द्वारा नहीं लिखा गया था, मैंने अभी कुछ बदलाव किए हैं। मैंने सोचा कि यह अजीब बात है कि व्यक्ति ने $-_P डेटा के बजाय $ _POST का उपयोग किया, मैं शायद इसे फिर से लिखूंगा। ऋण 2: हां, यह उपयोगकर्ता को दृश्य में रीडायरेक्ट करता है हालांकि मानक रीडायरेक्ट नहीं। मेरी मूल पोस्ट पर एक नज़र डालें। पाठ 3: मैं सहमत हूं, इसके अलावा आपका कोड बहुत अधिक सुरुचिपूर्ण है, तो मैं इसे कैसे कर सकता था। लय 4: मैं मॉडल का अधिक उपयोग करना शुरू कर दूंगा, यह निश्चित रूप से नियंत्रक को कम कर देता है और आसानी से पढ़ता है। – DanCake

+0

चरित्र सीमा को दबाएं और स्वरूपण की हानि इसे पढ़ने में थोड़ा मुश्किल बनाती है। लय 5: पेपैल आईपीएन वास्तव में एक नियमित एपीआई है, डेटा उपयोगकर्ता के साथ भेजा जाता है जो डेटासोर्स को लागू करने में मुश्किल हो सकता है। मुझे पता है कि दृश्य में कोड काफी खराब है, मुझे इसे पूरा करने का कोई और तरीका नहीं मिला। बोनस: मुझे वास्तव में केकपीएचपी एपीआई के ठीक दिखने चाहिए, मैंने HttpSocket घटक के साथ प्रयोग किया है लेकिन RequestHandler नहीं है। आपकी सभी मदद के लिए धन्यवाद फिर से – DanCake

+0

अच्छा उदाहरण कोड प्रदान करने के लिए धन्यवाद, मुझे वास्तव में यह सोचने में खुशी हुई कि कोई इसे कैसे पुन: सक्रिय कर सकता है। :) आपके बिंदुओं के जवाब में: 1. मैंने इसे $-- पैराम ['फॉर्म'] $ के रूप में अपडेट किया है-> डेटा केवल केक के फॉर्महेल्पर के साथ बनाए गए फॉर्मों द्वारा सबमिट किए गए डेटा पर लागू होता है; 2. यदि डेटा की मात्रा अत्यधिक नहीं है तो मैं यहां परीक्षण करता हूं यदि मैं यहां काम करता हूं। यदि पेपैल केवल पोस्ट स्वीकार करता है तो मैं केवल सुझाव दे सकता हूं कि आप जावास्क्रिप्ट सक्षम किए बिना उन लोगों के लिए सबमिट बटन जोड़ें; 3/4। यदि आपको इसे कुछ शब्दों में रखने की आवश्यकता है, तो इसे किसी के द्वारा "फैट मॉडल, पतला नियंत्रक" पद्धति के रूप में बनाया गया है; – deizel

1

अच्छी तरह से मैं इन दो चीजों का कहना था :

  1. आपके पास पूरा है हार्ड-कोडेड कॉन्फ़िगरेशन सामग्री के बहुत सारे ... केक के Configure का उपयोग करने के लिए ... $cost पहले नियंत्रक या $ paypalData में वैरिएबल की तरह ... आप इसे कहीं और से प्राप्त कर सकते हैं यदि आप चाहते हैं (उदाहरण के लिए फ्लैश चाहिए भाषा फाइलों से आते हैं), लेकिन कार्यान्वयन के साथ कॉन्फ़िगरेशन को मिश्रण न करें ... इससे कक्षाएं अधिक पठनीय हो जाएंगी, और रखरखाव बहुत आसान हो जाएगा ...
  2. सभी सॉकेट सामान को एक नए सहायक वर्ग में encapsulate ... आपको इसे कहीं और की आवश्यकता हो सकती है ... और वास्तव में, यह क्या होता है यह घटता है ... भी, उस बो बो नियंत्रक के अन्य हिस्सों को बाहर ले जाने पर विचार करें ... उदाहरण के लिए बस इसके तहत कुछ अन्य वर्ग को टकराएं, जो कार्यान्वयन करता है .. आपको हमेशा छोटे और संक्षिप्त फ्रंट नियंत्रकों को रखने की कोशिश करनी चाहिए, क्योंकि यह समझने में बहुत आसान होता है कि क्या होता है ... यदि कोई कार्यान्वयन विवरण की परवाह करता है, तो वह कोर में देख सकता है तालाब वर्ग ...

जो मुझे लगता है कि केशिश है ...

greetz

back2dos

+0

क्यों काम नहीं करता है, मैं अंततः कॉन्फ़िगर करने के लिए सबकुछ स्थानांतरित करने की योजना बना रहा हूं, यह बस था हार्डकोडेड जबकि मैं यह सुनिश्चित करने के लिए परीक्षण करता हूं कि सबकुछ काम कर रहा है। मुझे शायद भाषा फ़ाइलों में सब कुछ ले जाना शुरू करना चाहिए, हालांकि मुझे अभी तक उनके साथ खेलने का मौका नहीं मिला है। जबकि हम इस पर हैं, l10n और i18n के बीच मतभेद क्या है? – DanCake

+0

10 और 18 पहले और अंतिम अक्षर के बीच वर्णों की मात्रा का प्रतिनिधित्व करते हैं। वे क्रमशः एल-ओकलाज़ैटियो-एन और आई-नर्नेशनलिजेटियो-एन के लिए खड़े हैं। पूर्व वास्तव में किसी विशेष भाषा के लिए अनुवाद का निर्माण करने का कार्य होता है और बाद में किसी एप्लिकेशन को अनुवाद (या स्थानीयकृत) अनुवाद करने के लिए सक्षम बनाता है (यानी केक में __() फ़ंक्शन का उपयोग करके)। – deizel

+0

बढ़िया! मदद – DanCake

5

डीज़ेल (महान पोस्ट बीटीडब्ल्यू) द्वारा दी गई सभी चीजों को छोड़कर, मूल केक सिद्धांतों में से एक को याद रखें: वसा मॉडल, पतला नियंत्रक। आप this example देख सकते हैं, लेकिन मूलभूत विचार यह है कि अपने सभी मॉडलों में अपने सभी डेटा-मैंगलिंग सामान डालें। आपके नियंत्रक को (ज्यादातर) अपने मॉडल और विचारों के बीच एक लिंक होना चाहिए। आपका PremiumSitesController :: अनुक्रमणिका() किसी ऐसे चीज का एक आदर्श उदाहरण है जो आपके मॉडल में कहीं भी होना चाहिए (जैसा कि डीज़ेल द्वारा इंगित किया गया है)।

Chris Hartjes ने book about refactoring भी लिखा है, यदि आप वास्तव में सीखना चाहते हैं तो यह देखना चाहेंगे (यह मुफ़्त नहीं है, लेकिन यह सस्ता है)। इसके अलावा, Matt Curry में एक अच्छा नाम है: Super Awesome Advanced CakePHP Tips, और यह डाउनलोड के लिए पूरी तरह से नि: शुल्क है। दोनों एक अच्छी पढ़ाई के लिए बनाते हैं।

मैं केक के बारे में अपना लेख भी प्लग करना चाहता हूं जिसे मुझे विश्वास करना पसंद है केक में कोड गुणवत्ता के लिए महत्वपूर्ण है: Code formatting and readability। हालांकि मैं समझता हूं कि लोग असहमत हैं .. :-)

+0

आप वह व्यक्ति हैं जिन्होंने न्यूट्रीनो सीएमएस बनाया है? मैंने पाया कि शुरुआत के करीब और कोड को देखकर बहुत मदद मिली है। इसके अलावा, मैं निश्चित रूप से अगले कुछ दिनों में उनको पढ़ूंगा। – DanCake

+0

हाँ, यह मैं होगा :) मुझे खुशी है कि यह आपको सीखने में मदद करता है, यह निश्चित रूप से मेरी मदद करता है। हालांकि हाल ही में, अतिरिक्त समय दुर्लभ रहा है .. –

संबंधित मुद्दे