نوشتن ماژول در دروپال 7 - بخش شش

ماژول نویسی دروپال بخش یک
ماژول نویسی دروپال بخش دو
ماژول نویسی دروپال بخش سه
ماژول نویسی دروپال بخش چهار
ماژول نویسی دروپال بخش پنج
ماژول نویسی دروپال بخش هفت
ماژول نویسی دروپال بخش هشت

در بخش قبل متوجه شدیم که db_insert وجود داده‌های قبلی رو بررسی نمی‌کنه، پس باید این کار رو دستی انجام بدیم. برای این کار دو راه وجود داره: استفاده از db_select یا استفاده از db_query. تابع db_select، شیئی رو به ما برمی‌گرداند که می‌تونستیم شرایط(condition) یا joinها یا سایر قابلیت‌های پایگاه داده رو به صورت کدهای PHP تنظیم کنیم. اما مشکلی که در تایع db_select وجود داره، بازدهی پایین‌تر این تابع هست. برای انجام queryهای ساده‌تر، می‌تونیم از تابع db_query استفاده کنیم. برای اجرای db_query نیاز به نوشتن دستی کد نهایی SQL داریم که مزیت‌هایی که قبلا گفتیم رو نداره. پس همیشه باید برای انتخاب یکی از این دو ابزار سادگی کار و یا بازدهی بیشتر رو در نظر بگیریم.

بعد از اجرای query مورد نظر، با بررسی خالی بودن db_select یا db_query متوجه می‌شیم داده‌ای وجود نداره و در صورتی که این توابع یک user_id یا uid که قبلا در جدول پایگاه داده ذخیره کردیم برگرداندند، می‌فهمیم باید اطلاعات کدام سطر جدول رو به روز کنیم. به این صورت:

  $uid = $GLOBALS['user']->uid;

  if (isset($form_state['clicked_button']) && $form_state['clicked_button']['#name'] == 'op') {
    $v = $form_state['values'];
    $result = _my_calculator_calculate($v['value_1'], $v['value_2'], $v['operation']);

    $fields = array('uid' => $uid,  'result' => $result);

    // First check to see if there is already anything in DB.
    $check = db_query('SELECT rid FROM {my_calculator_result} WHERE uid=:uid LIMIT 0,1',
        array(':uid' => $uid));
    $check = $check->fetchAssoc();
    
    if(empty($check)) {
      $query = db_insert('my_calculator_result')
        ->fields($fields);
        ->execute();
    }
    else {
      $query = db_update('my_calculator_result')
        ->fields($fields)
        ->condition('rid', $check['rid'])
        ->execute();
    }
  }
  else {
    $query = db_select('my_calculator_result', 'mcr')
      ->condition('mcr.uid', $uid)
      ->fields('mcr', array('result'))
      ->range(0, 1)
      ->execute();

    $result = $query->fetchAssoc();
    $result = $result['result'];
  }

توضیحات:

  • متغیر $GLOBALS در PHP آرایه‌ایست از همه‌ی متغیرهای سراسری. در دروپال، یک متغیر سراسری به اسم user وجود داره، که در این آرایه اندیس 'user' هست. خود user شیئی از کلاس stdClass هست. با گرفتن یک dpm از $GLOBALS میتونید همه‌ی متغیرهای سراسری موجود در دروپال و با dpm از اندیس کاربر (user) می‌تونید ببینید این شیئ حاوی چه اطلاعاتیه. چیزی که ما نیاز داشتیم، شناسه‌ی کاربر یا uid اون بود. توجه کنید که uid برای کاربران ناشناس همیشه 0 هست. در php راه دیگری هم برای دسترسی به متغیرهای موجود در $GLOBALS وجود داره: استفاده از اعلان global به این صورت: global $user که بعدا می‌تونیم به uid به این صورت دسترسی پیدا کنیم:x $user->uid.
  • در عبارت if اول، یعنی در صورتی که فرم حاوی مقادیری وارد شده توسط کاربر بود، ابتدا بررسی می‌کنیم که در پایگاه داده اطلاعاتی توسط این کاربر ذخیره شده یا نه. برای اینکار از db_query استفاده کردیم. نکته‌ی مهمی در استفاده از این تابع وجود داره برای جلوگیری از sql injection یا طزریق sql، شناسه‌ی کاربر رو به صورت place holder در عبارت SQL مشخص کردیم، و در آرایه‌ای به عنوان آرگومان دوم db_query، مقدار این place_holder رو تعیین کردیم. place_holder ها برای queryها با علامت دونقطه «:» و کلمه‌ای به عنوان نام place_holder تعیین می‌شن.
  • نام جدولی که در عبارت SQL ارسالی به db_query ذکر کردیم درون آکولاد {} قرار گرفت. دلیل این کار این بود که جداول دروپال در پایگاه داده می‌تونن دارای پیشوند باشن (اگر به یاد داشته باشید، در طول فرایند نصب، دروپال پیشوند جداول رو در حین تعیین نام پایگاه داده از شما می‌پرسه که می‌تونید اون رو خالی بگذارید). با این کار به دروپال فرصت می‌دیم این پیشوند رو به نام جداول اضافه کنه.
  • با عبارت LIMIT 0,1 مجموعه‌ی جواب رو به حداکثر یک جواب محدود کردیم، پس بعد از اجرای db_query، اگر نتیجه‌ای در پایگاه داده پیدا شود، در آرایه‌ای قرار خواهد گرفت که شامل یک مقدار خواهد بود، اندیس این آرایه همان rid و مقدار این اندیس، سطر مربوط به uid تعیین شده است. اگر نتیجه‌ای پیدا نشد، $check برابر با FALSE خواهد بود.
  • در بخش بعد، اگر $check خالی بود، طبق روال قبل پیش میریم اما اگر حاوی مقدار بود، معلوم میشه باید سطری در پایگاه داده به روز بشه. برای این کار از تابع db_update استفاده می‌کنیم. دستور db_update مشابه db_insert هست. اما باید همیشه یادمون باشه اگر condition رو تعیین نکنیم همه‌ی سطرهای جدول با مقدار مشخص شده به روز می‌شن! در نهایت هم $query که db_uodate به اون اختصاص پیدا کرده حاوی تعداد سطرهای به روز شده خواهد بود.
  • در db_update از همان rid پیدا شده برای به روز رسانی استفاده کردیم اما لزومی نداشت، می‌تونستیم از همان uid استفاده کنیم، اما استفاده از rid به ما اجازه‌ی توسعه راحت‌تر ماژول در آینده رو می‌ده.
  • در هیچ یک از متدهای condition بالا، آرگومان سوم که عملگر رو تعیین میکنه، نوشته نشد، چون پیش‌فرض اون عملگر تساوی «=» برای مقادیر مشخص و عملگر «IN» برای آرایه‌هاست پس عملگر تساوی به صورت خودکار برای ما انتخاب میشه.

کاری که باید انجام بدید حذف مقادیر تکراری برای یک کاربر با uid مشخص از پایگاه داده است که به خاطر باگ موجود در کدهای قبلی به وجود آمده بود. یک راه نصب مجدد ماژول با دستور drush dre my_calculator در طورت نصب بودن devel و راه دیگر، خالی کردن (truncate table my_calculator_result) جدول ماژول از پایگاه داده است.

اما راه آسان‌تری هم برای انجام کدهای بالا با استفاده از db_merge وجود داره. این تابع، وظایف مربوط به «بررسی و به روز رسانی در صورت وجود داشتن رکوردهای قبلی و در غیر اسن صورت ایجاد رکورد جدید» رو در یک تابع خلاصه می‌کنه. پس کد بالا به این صورت تغییر پیدا می‌کنه.

  $uid = $GLOBALS['user']->uid;

  if (isset($form_state['clicked_button']) && $form_state['clicked_button']['#name'] == 'op') {
    $v = $form_state['values'];
    $result = _my_calculator_calculate($v['value_1'], $v['value_2'], $v['operation']);

    $fields = array('uid' => $uid,  'result' => $result);

    db_merge('my_calculator_result')
      ->key('uid' => $uid)
      ->fields($fields)
      ->execute();
  }
  else {
    $query = db_select('my_calculator_result', 'mcr')
      ->condition('mcr.uid', $uid, '=')
      ->fields('mcr', array('result'))
      ->range(0, 1)
      ->execute();

    $result = $query->fetchAssoc();
    $result = $result['result'];
  }

توضیحات:

  • در استفاده از db_merge، به جای تعیین condition، از متد key برای تعیین اینکه آیا رکوردی از قبل در پایگاه داده وجود داشته یا نه استفاده می‌کنیم. آرگومان متد key یک آرایه‌ی associative از کلیدها و مقادیرشون هست.
  • توصیه میکنم صفحه‌ی این تابع در دروپال رو مطالعه کنید تا با سایر قابلیت‌های این تابع هم آشنا بشید. مثلا این تابع می‌تونه در صورتی که رکوردی از قبل وجود داشت فقط یک فیلد از این رکورد رو به روز رسانی کنه و به بقیه فیلدها کاری نداشته باشه.
  • متد MERGE در پایگاه داده‌های معمول در واقع وجود نداره (یا دارای دستورهای عجیب و غریبه) و دروپال با db_merge این دستور SQL رو با دستورهای موجود شبیه‌سازی می‌کنه.

ادامه دارد...

شاد و موفق باشید.

تگ های مطلب: 

دسته بندی مطلب: 

دیدگاه‌ها

Keep Going

عالی!!! یه پیشنهاد !!! لطفا یکسری آموزش واسه معرفی یکی از بهترین ماژولهای ساخت فرم (مثلا entityform) بذارید و نحوه فول ایجکس کردنش در صورت وجود به وسیله ماژول آماده و در صورت عدم وجود ماژول، توی آموزش خودتون زحمت نوشتن ماژولشو بنویسید. به دلیل ماهیت کار اگه چنین آموزشی نوشته باشه من به عنوان عضو کوچکی از خانواده دروپال، نحوه نوشتن ماژول حرفه ای دروپال متوجه میشم. به اصطلاح دوزاریم میافته. بازم ممنون بابت فعالیتتون تا اینجای کار...

خوشحالم که مقاله‌ها براتون

خوشحالم که مقاله‌ها براتون مفید بوده. ماژول‌هایی مثل entity form فقط برای ساختن form نیستن، قوانین گردش کار (workflow) رو هم به اون‌ها اعمال می‌کنن. ماژول‌هایی مثل form builder هم وجود دارند که راستش خیلی وقته ازشون استفاده نکردم، باید دوباره بررسیشون کنم ولی فکر کنم برای ایجاد ساختار اولیه فرم بود؟ و کاری برای ajaxی کردنش نمی‌کرد. برای اضافه کردن این قابلیت به فرم‌های پرکاربرد مثل فرم دیدگاه، ساخت محتوا، فیلترهای views ماژول‌هایی هستن اما ماژول کلی فکر نمی‌کنم وجود داشته باشه. اما نوشتن فرم‌های Ajaxی و استفاده از فرم‌ورک ajax (که در دروپال وجود داره و خیلی هم سادست) رو حتما در مقاله‌های بعدی توضیح میدم.

آموزش

ممنون از زحماتتون. اما تا اونجایی که اطلاع دارم، ایجکس کردن با Ajax API دروپال موقع ارسال ریکوئست های ایجکس، در جواب (Response) باید فرم رو Rebuild کنه. اگه اینطوری باشه خیلی جالب نمیشه. چون یه باره بخشی از فرم تغییر میکنه. فرمی که فول ایجکس باشه و ریسپانسها رو با جیکوئری بشه کنترل کرد و تغییرات کوچک توی فرم داد بهتر و حرفه ای تر نیست؟

برای اجرای eventهای مختلف بر

برای اجرای eventهای مختلف بر اساس response سرور (به جای relpace کردن بخش‌هایی که در tag تعیین شده‌ی HTML قرار می‌گیرن) فکر کنم نزدیکترین چیزی که داریم Autocompleteها باشن، چیز دیگه‌ای به ذهنم نمی‌رسه. برای اینکار فکر نمی‌کنم Form API کار خاصی کرده باشه. احتمالا مجبور باشیم دستی eventها رو به Drupal.behaviors وصل کنیم. که برای اینکار هم میشه از خصیصه‌ی attached در فرم‌ها برای هر المان استفاده کرد. ازاونجایی که ajaxی کردن فرم‌ها به این صورت و دستی سخته شاید بهتر باشه دنبال یک فرم‌ورک javascript خوب بگردید و به همه‌ی المان‌های فرم‌ها در hook_element_info_alter متصلش کنید. کار کمی هم نیست واقعا! یه راه ممکن دیگه هم اینه که یک library از تغییراتی که روی المان‌های معمول فرم مثل textfield صورت میگیره ایجاد کنید و با همین ajaxframework در دروپال این کدهای جاوااسکریپت رو برای ارسال بفرستید. initiator هم به Drupal.behaviours متصل میشه. اما قبل از انجام این کار بهتره حتما در IRC دروپال (روی freenode سرو میشه) سوال بپرسین که با فریم‌ورک جاری ممکن نیست؟

این توابع توی فریم‌ورک هست:

این توابع توی فریم‌ورک هست: ajax_command_css و ajax_command_invoke که می‌تونید دستورات jquery رو اجرا کنید، اما بازهم به اون خوبی که من و شما دوس داریم باشه! نیست. باید Behaviourها و actionهای jquery رو قبلا دستی اضافه کرده باشیم... و اما برای commandهای سفارشی در ajax: https://isovera.com/blog/create-custom-commands-drupal-7-ajax-framework و http://www.jaypan.com/tutorial/calling-function-after-ajax-event-drupal-7

Ajax Comment

بسیار ممنون. در مورد راه حل اولتون همانطور که گفتید کار دشوار هست. شاید توی دروپال 8 برخی از این کاستی ها برطرف شده باشه، البته من هنوز مستندات نسخه 8 رو مطالعه نکردم. اما امیدوارم تغییرات خوبی انجام شده باشه. و در مورد راه حل آخر، آره همونطور که گفتید باید Custom Command ایجاد بشه. اما به نظرتون بهتر نیست که با یک ماژول اینکار رو انجام بدیم و با یک درخواست ایجکس به آدرس ماژول جواب ها رو به صورت json دریافت کنیم؟

در کل راحت‌ترین چیزی که به

در کل راحت‌ترین چیزی که به نظر من می‌رسه، اینه که همه‌ی دستورات رو روی clientها بارگذاری کنیم (مثلا تغییر رنگ پس‌زمینه با jquery) بعدش آقای ajax_command_invoke فقط باعث trigger شدن actionهای تعریفی ما بشه. خوبیش اینه که درست طراحی شده و آرگومان‌هایی مثل رنگ پس زمینه چی باید باشه رو هم می‌گیره. چون بازهم مشکل دستی بودن رو داریم اگه بتونین یه چندتا مثال از کارهایی که باید بشه توی فرم‌های Full ajax انجام داد بزنین، خیلی واضح‌تر می‌شه تا چه مقدارش رو خودمون مجبوریم پیاده‌سازی کنیم تا چه مقدارش هم توی دروپال پیاده‌سازی شده. باید بررسی کنیم چه کارهایی کلی هستن و میشه سپردشون به یک API. ایده‌آل فکر کنم اینجور چیزی باشه: $form['my_important_element'] => array(   .   .   . '#ajax_handlers' => array('_my_ajax_background_handler'), '#ajax_callback_attr' => array('bg_on_validation_failure:RED'), ); اگه بخوایم Handler ها رو برای فرم بزاریم توی ماژول جداگانه با پیاده‌سازی همه‌ی JSON و .... در واقع بخش زیادی از قسمت‌های پیاده‌سازی شده رو دوباره پیاده‌سازی کردیم. این هم email من هست اگه لازم بود بیشتر صحبت کنیم:  koosha.hosseiny@gmail.com

Ajax Comment

آره. حرفهاتون کاملا مورد قبوله. بابت پاسخگویی و ایمیل خیلی ممنون. حتما مزاحمتون میشم... موفق باشید.

آموزش php

سایتتون فوق العادست.واقعا بی نظیرید.خیلی از مطالبتون استفاده می کنم و لازمه یه تشکر ویژه داشته باشم.

دیدگاه جدیدی بگذارید

آخرین ارسال ها

محتواهای محبوب

درباره ما

Author
اینجا دروپال یعنی همه چیز. در مورد دروپال صحبت میکنیم. ماژول هامون رو به اشتراک میزاریم در مورد قالب دروپال ، فروشگاه دروپال، دروپال فارسی و تاریخ شمسی دروپال صحبت میکنیم و هرچیزی که در مورد طراحی سایت با دروپال میدونیم به هم انتقال میدیم. دروپالیون یک سایت شخصی نیست. ما دست همه کسانی که برای پیشرفت دروپال تلاش میکنند رو میفشاریم و با آغوش باز اونها رو در این سایت میپذیریم.

تماس با ما

با ما تماس بگیرید.

logo-samandehi