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

By کوشا حسینی, 24 سپتامبر, 2013

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

در حالی که از فرم‌ها می‌توان برای نمایش اطلاعات استفاده کرد، دلیل اصلی استفاده از آن‌ها ورود داده است. در این بخش به ورود داده توسط فرم‌ها در کاربر می‌پردازیم.

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

در این بخش، یک ماشین حساب ساده با استفاده از فرم‌های دروپالی می‌نویسیم. برای مروری بر مطالب گفته شده، ماژول جدیدی تعریف می‌کنیم. فایل info به این شکل خواهد بود. با مطالب توضبح داده شده شما باید بتونید همه‌ی محتوای این فایل رو متوجه شوید:

name = My calculator
description = A basic calculator using drupal forms.
core = 7.x

فایل module

<?php
/**
 * @file
 * Main module file for "My calculator". Provides a basic calculator using drupal forms.
 */

/**
 * Implements hook_menu().
 */
function my_calculator_menu() {
  // Main calculator page, With anyone allowed to access.
  $items['mycalc'] = array(
    'title' => 'My calculator',
    'access callback' => TRUE,
    'page callback' => 'drupal_get_form',
    'page arguments' => array('_my_calculator_main_form'),
 );

  return $items;
}

چند توضیح: در ابتدای فایل، با فرمت doxygen در مورد فایلی که نوشتیم توضیح دادیم. در بالای تابع هوک منو، باز با فرمت doxygen، جمله‌ی Implements hook_WHAT() رو که WHAT با نام هوک جایگزین می‌شود رو نوشتیم. این کار قانون تعیین شده‌ی دروپال برای نوشتن Documention برای هوک‌های پیاده‌سازی شدست. آیتمی که تعریف کردیم، از طریق مسیر http://mysite/mycalc قابل دسترسی هست، از آنجایی که برای access arguments، مقدار TRUE را قرار دادیم، همه‌ی بازدیدکنندگان حتی کاربران ناشناس قادر به دسترسی به این صفحه هستند. تابع مسئول مدیریت این مسیر رو drupal_get_form قرار دادیم و آرگومان ارسالی به اون رو _my_calculator_main_form تعیین کردیم. یادمون نره که return $items رو هم بنویسیم!!!. بعد از این کار لازمه حافظه‌ی موقت منو رو با پاک کردن حافظه‌های موقت (یا drush cc menu) پاک کنیم تا این آیتم شناسایی شود. البته در زمان فعال کردن ماژول‌ها این کار به صورت خودکار انجام میشه.

اصل مطلب! فرم.

با یک ماشین حساب ساده شروع می‌کنیم (دوستم اینجا نشسته بود گفت ماشین حساب خیلی دروپالی نیست، اما فعلا چیز بهتری به ذهنم نمی‌رسه! شاید بهتر باشه توی مقاله‌ی بعدی داده‌هامون رو توی پایگاه داده‌ ذخیره کنیم تا دروپالی‌تر بشه).
برای یک ماشین حساب خیلی ساده، نیاز به ورود دو متغیر توسط فیلدهای متنی، فهرست انتخاب عملگری که عملی روی داده‌ها انجام می‌ده و دکمه‌ی ارسال اطلاعات داریم. برای پیدا کردن المان‌های مورد نظر، به مرجع المان‌های فرم مراجعه می‌کنیم. این مرجع همیشه با جست‌وجوی عبارت Form API یا Drupal form API در گوگل به سادگی پیدا می‌شه. این صفحه‌ی مرجع هست. المان‌های مورد نظر ما:

  • select برای انتخابگر عملگر. نیازمند خصیصه‌های اجباری #title عنوان المان، #options یک آرایه‌ی associative برای گرینه‌های قایل انتخاب، خصیصه‌های اختیاری #description برای توضیحات،‌ #default_value برای مقدار پیش‌فرض در هنگام بارگذاری فرم، #required برای اجباری کردن انتخاب یک گزینه.
  • textfield برای ورود متغیرها. #title برای عنوان و خصیصه‌های اختیاری که در بالا ذکر شد به علاوه #maxlength برای حداکثر طول داده‌ی مجاز.
  • action برای ارسال اطلاعات. action‌ها با دکمه‌ها تفاوت دارن، درواقع یک container برای دکمه‌ها و لینک‌هایی هستند که مربوط به وظایف فرم میشه. استفاده از action این امکان رو به پوسته‌ها می‌ده که قالب‌بندی صحیح رو برای دکمه‌ها و لینک‌های فرم در نظر بگیرند.
  • button برای دکمه‌ی محاسبه با خصیصه‌ی #value به عنوان متن نمایش داده شده در دکمه.

در نهایت فرم ما به این صورت خواهد بود:

function _my_calculator_main_form($form, &$form_state) {
  $form['value_1'] = array(
    '#type' => 'textfield',
    '#title' => t('First value'),
    '#maxlength' => 12,
    '#required' => TRUE,
  );

  $options = array('+', '-', '/', 'x');
  $form['operation'] = array(
    '#type' => 'select',
    '#options' => drupal_map_assoc($options),
    '#title' => t('Operation'),
    '#required' => TRUE,
    '#description' => t('The operation applied to first and second value.'),
  );
  
  $form['value_2'] = array(
    '#type' => 'textfield',
    '#title' => t('Second value'),
    '#maxlength' => 12,
    '#required' => TRUE,
  );

  $form['actions'] = array('#type' => 'actions');
  $form['actions']['calculate'] = array(
    '#type' => 'button',
    '#value' => t('Calculate'),
  );

  return $form;
}

چند توضیح:

  • ما هیچ رشته‌ی متنی رو به صورت فارسی وارد نکردیم، مثل عنواین المان‌ها و توضیحاتشون. در عوض رشته‌ی مورد نظر رو در تابع t قرار دادیم. این کار، باعث می‌شه این رشته‌ها در صفحه‌ی ترجمه ظاهر بشن و به زبان‌های مختلف قابل ترجمه باشن. از مزیت دیگه اینکار اینه که اگر فردی که به زبان فارسی صحبت نمی‌کنه کدهای مارو بخونه بازهم از همچی سر در میاره.
  • ترتیب نمایش المان‌هایی که تعریف کردیم، همان ترتیب قرار گرفتن اونها در آرایه‌ی فرم هست.
  • المان button به صورت مستقیم در فرم تعریف نشد، بلکه المان action رو به عنوان المان والد اون در نظر گرفتیم. فرم‌ها ساختار لانه‌ای (nested) دارن، یعنی هر المان می‌تونه والد تعداد نامحدودی المان دیگه باشه. در اینجا المان action والد تنها المان button شده.
  • هیچ وقت (یا ۹۹.۹ درصد مواقع!) به برگرداندن $form_state از تابع احتیاج پیدا نمی‌کنیم. چون این تایع توسط علامت & به عنوان آرگومان از نوع مرجع تعریف شده، و هر تغییری در form_state باعث تغییر در form_state اصلی می‌شه. بعدا در مورد این آرایه بیشتر صحبت می‌کنیم.
  • از تابعی به نام drupal_map_assoc استفاده کردیم، کار این تایع چیست؟ این تابع راهی استاندارد برای تبدیل آرایه‌های تک‌بعدی به دوبعدی (به قول برنامه‌نویسان c!) یا آرایه‌هایی با اندیس‌های عددی به آرایه‌های انجمنی یا associatice هست. توضیحش طولانیه اما کارش سادست! آرایه‌ای مثل array('a', 'b','c') x رو به آرایه‌ای مثل array('a'=>'a', 'b'=>'b', 'c'=>'c') x تبدیل می‌کنه. اما چرا آرایه‌ای به این شکل لازمه؟ Form API از خود اندیس برای تشخیص مقداری که کاربر انتخاب می‌کنه و از مقدار اندیس برای نمایش اون در فرم استفاده می‌کنه.
    یعنی اگر آرایه‌ای مثل array('options1' => t('This is option one you will select')) x به فرم بدیم، مقداری که نمایش داده می‌شه This is option one you will select یا ترجمه‌اش و مقداری که در فرم ذخیره می‌شه options1 خواهد بود، نه اون عبارت طولانی و حتی ترجمه شده! پس همیشه مقدارش یکتا و کار باهاش راحته.

انجام عملیات روی مقادیر فرم (محاسبه نتیجه ریاضی برای فرم ما)

اولین پیشنهادی که برای شما دارم، استفاده از تابع dpm در ابتدای فرم به شکلdpm($form_state, 'form_state values'); :x هست. با اینکار مقدار اولیه این تابع و مقادیر بعدی اون پس از زدن دکمه‌ی Calculate رو به وضوح می‌بینید.
در ابتدای نمایش فرم، اگر از dpm استفاده کرده باشید، می‌بینید که $form_state شامل اندیس input هست که خالیه. با ثبت فرم، مقادیر جدیدی اضافه می‌شن:

  • اندیس input با مقادیر وارد شده‌ی کاربر پر می‌شه. اما ما هیچ وقت از این اندیس استفاده نمی‌کنیم، چرا که در واقع این مقادیریست که توسط form_api هنوز sanitize یا اعتبارسنجی نشده و می‌تونه شامل داده‌های مخرب باشه.
  • اندیس values شامل داده‌هایی که توسط کاربر وارد شده به صورت امن و قابل استفاده
  • complete form ارجاعی به $form در زمان ثبت توسط کاربر
  • triggering_element المانی که باعث ارسال اطلاعات به سایت شده. وقتی که چندین دکمه در فرم وجود داره (مثل cancel و delete) مهمه که بدونیم انتخاب کاربر چی بوده. اما المان ارسال کننده همیشه دکمه نیست بلکه رویدادهای ajax هم می‌تونن باعث ارسال بشن که از حوصله‌ی این مقاله خارجه
  • clicked_button یا دقیقا دکمه‌ای که کلیک شده. کاربردی مشابه triggering_element داره اما دقیقا مشخص کننده‌ی دکمه کلیک شده است.

با بررسی اندیس values، این موارد رو می‌بینیم

  • form_build_id، form_token، که برای تشخیص فرم ثبت شده توسط کاربر به صورت داخلی در form api برای اعتبارسنجی‌ها و جلوگیری از CSRF استفاده می‌شه.
  • value_1، value_2، operation داده‌های مورد انتظار ما از کاربر
  • op عملیاتی که انتخاب شده، همان Calcualte.

با نکته‌ی عجیبی روبرو هستیم، فرم ثبت شده به همان تابع فرم‌ساز ما ارسال شد. چرا؟ در یک توضیح خیلی ساده، با اینکار بر حسب مقادیر وارد شده، می‌توان المان‌های فرم رو تغییر داد، و فرم‌های چند مرحله‌ای ایجاد کرد. مثلا اگر فهرست انتخابی از رشته‌های درسی به کاربر نشان دادیم و کاربر درسی با کد ۳۱۰۱ انتخاب کرد در مرحله‌ی بعد این فرم می‌تونیم المانی از اساتیدی که این درس رو ارائه می‌کنن در فرم قرار بدیم. البته با Ajax بدون ثبت فرم هم امکان‌پذیر هست، که در قسمت‌های بعدی به اون می‌پردازیم.
کاری که ما انجام می‌دیم (که البته متدهای بهتر دیگری هم برای اینکار وجود داره) اینه که وقتی کاربر مقادیری رو وارد کرد، و form_state دارای مقدار بود نتیجه رو در فرم قرار بدیم. پس تابع فرم‌ساز به این صورت تغییر می‌کنه:

function _my_calculator_main_form($form, &$form_state) {
  $result = NULL;
  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']);
  }

  $form['value_1'] = .........
  // Defination of value_1, value_2 and operation here.

  if ($result !== NULL) {
    $form['result'] = array(
      '#type' => 'item',
      '#markup' => t('The result of calculation is %result', array('%result' => $result)),
      '#prefix' => '<h3>',
      '#suffix' => '</h3>',
    );
  }

  $form['actions'] = array('#type' => 'actions');
  $form['actions']['calculate'] = array(
    '#type' => 'button',
    '#value' => t('Calculate'),
  );

  return $form;
}

توضیحات:

  • در ابتدای فرم مقدار result رو NULL قرار می‌دیم. استفاده از NULL و عبارت !== به جای != به ما امکان تشخیص عدم وجود جواب از جواب 0 رو میده.
  • در عبارت if، اگر کاربر دکمه‌ای رو کلیک کرده، clicked_button به فرم اضافه خواهد شد. و سپس اگر نام دکمه‌ی کلیک شده op بود (توجه کنید که #name متد استاندارد برای تشخیص چندین دکمه از هم است که چون ما #name رو به تعریف دکمه اضافه نکردیم، مقدار پیش‌فرض op را دارد. از #value نباید استفاده کنید چون ممکن است ترجمه شود و همیشه عبارت انگلیسی شما نباشد) سپس؛ متغیر $v رو برای دسترسی آسان‌تر به مقادیر تعریف کردیم، تایع _my_calculator_calculate رو با مقادیر داده شده صدا زدیم و نتیجه رو در result قرار دادیم.
  • بخش‌های بعدی کد برای خوانایی بیشتر نمایش داده نشده.
  • درست قبل از تعریف دکمه‌ها، اگر جوابی وجود داشته باشد، یک المان ساده‌ی متنی حاوی جواب استفاده می‌کنیم. این عبارت داری دو خصیصه‌ی #prefix و #suffix هست که باعث می‌شه متنی به قبل و بعد از المان افزوده بشه. این روش، راهی متداول برای افزودن تگ‌های HTML به دور المان‌ها هم محسوب می‌شه. در اینجا با اینکار ما المان رو در تگ H3 در HTML قرار دادیم تا جواب بزرگ‌تر به نظر بیاد.
  • متنی که به عنوان جواب نمایش داده می‌شه، از تابع t گرفته می‌شه. عبارت result% و آرگومان دوم t نظر رو جلب می‌کنه اما چه معنی داره؟ هنگامی که متنی رو ترجمه می‌کنیم که بخشی از اون معلوم نیست، از placeholderها استفاده می‌کنیم. placeholderها جای عبارت نامعلوم می‌شینن و در هنگام نمایش عبارت ترجمه شده، placeholderها با مقادیر واقعی که در زمان نمایش معلوم شده‌اند جایگزین می‌شن. مقدار نامعلوم ما %result نامیده شده، و در آرگومان دوم t با یک آرایه مقدار واقعیش پس از محاسبه مشخص میشه. استفاده از % باعث میشه مقدار نامعلوم در <em> گذاشته بشه. استفاده از @ از هیچ تگی استفاده نمیکنه و استفاده از ! میتونه یک مشکل امنیتی حساب بشه! چرا که باعث می‌شه تابع t متن درحال جایگزینی رو برای تگ‌های خطرناک html مثل script بررسی نکنه. در واقع تابع کوچولوی t، یک سپر امنیتی مهم دروپال برای اعتبارسنجی رشته‌ها هم حساب می‌شه.

آخرین کاری که باید انجام بدیم، نوشتن تابع مسؤل محاسبه نتیجه است که می‌تونید مستقیما بعد از تابع فرم‌ساز اضافه کنید:

function _my_calculator_calculate($v0, $v1, $op) {
  switch ($op) {
    case '+':
      $result = $v0 + $v1;
      break;

    case '-':
      $result = $v0 - $v1;
      break;

    case '/':
      $result = $v0 / $v1;
      break;

    case 'x':
      $result = $v0 * $v1;
      break;
  }

  return $result;
}

فایل ماژول کامل هم در فایل ضمیمه آمده است.

در سری های بعد نحوه ارتباط با پایگاه داده را بررسی میکنیم.

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

تگ های مطلب
دسته بندی مطلب