Требования по разработке модулей под систему Айтинити на базе CMS/CMF Drupal 7.x
Данный документ следует рассматривать как требования по разработке под Айтинити, т.к. имеются расхождения с некоторыми стандартами Drupal.
0: Формат файлов.
Переносы строк - формат Unix.
Никакой кодировки UTF8 файлов скриптов (кроме файлов *.po). Используем стандартную ANSI/cp1251. Это касается так-же js и css.
Никакой кириллицы в .css и .js файлах.
Никакой кириллицы в строковых константах (используем переводы, см.ниже). Но если возникла такая непреодолимая потребность - используем drupal_convert_to_utf8(‘текст’, ‘cp1251’);
Кириллица допустима (но нежелательна) в комментариях PHP, но при этом - настройте свои редакторы. Исключите автоматическое перекодирование файла редактором в UTF8 при попадании в него кириллицы.
Ни в коем случае не использовать закрывающий тэг “?>” в конце PHP кода.
1: Оформление кода
Смотрим исходники Drupal и делаем так-же. Если вы привыкли по другому - отучайтесь. Чем быстрее привыкните к концепции системы, под которую разрабатываете, тем лучше будут восприниматься ее исходники.
Отступы - один TAB = 2 пробела. Настройте свой редактор на использование пробелов в табуляции.
Выключаем в редакторе (и голове) вертикально-колоночное форматирование массивов и приравниваний, что-бы на выходе не получалась подобная красота:
$array1 = array(
$a => ‘aaa’,
$b => ‘bbb’,
);
$array2 = array(
$a => ‘qwerty’,
$very_very_very_long_name_variable => ‘sss’,
);
А так-же
не if() а if ()
не foreach() а foreach ()
Но не function func ( $param1 ) а function func($param1)
Не ‘abcde’.$x, а ‘abcde’ . $x.
Не $a+$b, а $a + $b
Но не $node -> title, а $node->title
Щедро разделяем код на блоки.
Не
if ($a && $b) func($c);
, а
if ($a && $b) {
func($c);
}
Статичные массивы объявляем с запятой у последнего элемента (например array(1,2,3,4,5,)). В маленьких массивах это необязательно. Это не касается javascript, там запятая в конце недопустима (косяк будет в ИЕ)
Короткие массивы оформляем в одну строку, длинные - с новой строкой для каждого элемента.
Все названия функций должны начинаться с названия модуля. Глобальные константы - туда-же, не скупимся на буковки.
Именование собственных (helper) функций для специфического использовании только в пределах своего модуля должны начинаться с символа _подчеркивания_. Еще раз смотрим код ядра, как это обычно делается там.
2: Комментарии кода
Смотрим исходники друпала и делаем по подобию. Функции, классы и константы желательно комментировать. Но при этом не пишем пространные рассказы и повести как в ядре, только если объявленная функция сложна в понимании и использовании. Непонятные незамыленному глазу телодвижения с хитрой логикой в телах функций, так-же, комментируем. Главное - не расшибить лоб. Не пишем бред из серии капитан очевидность, типа:
if ($a == $b) { // if $a equal to $b
$node = node_load($a); // we load node object.
}
3: Написание кода
Именование функций и переменных должно быть интуитивно понятным (хороший код не нуждается в комментариях).
Не используем short open tags (‘<? ’, ‘<?= ’).
При написании JavaScript плагинов используем API jQuery версии v1.4.4, который по умолчанию включен в составе ядра. При этом, допустимо использование сторонних плагинов которым требуется версия jQuery выше 1.4.4, для поддержки таких плагинов есть модуль jquery_update на drupal.org, или наш оптимизированный и упрощенный jquery_latest.
При написании модуля не ввергаем его в зависимости от чрезмерной кучи сторонних модулей, подмодулей и недомодулей, которые нужно дополнительно скачивать, ставить и терпеливо настраивать, как это принято на drupal.org. Наша система ориентирована больше на простых пользователей, чем на программистов, чего не скажешь об админ-интерфейсах views, ubercart и прочего, выкладываемого на drupal.org.
Модуль не должен зависеть от каких-то своих доп. записей в глобальном .htaccess или php.ini. Так-же, условие “а вот надо там галочку поставить (создать тип материала, прописать в админке, etc), чтобы мой модуль перестал глючить” не имеет права на существование. Все должно работать из коробки.
4: Перевод интерфейса
Все статичные тексты, прописанные в модуле и отображаемые пользователю (заголовки элементов форм, названия страниц, описания и т.п., кроме пунктов меню или иных строк, загружаемых из БД) должны быть обернуты в функцию t(“текст на английском языке”) или format_plural(параметры). Никаких русских букв и слов в строках - все на английском. Если с английским плохо - переводчик гугл в помощь, и попросите кого-нибудь проверить грамотность построения предложений.
В javascript используем аналог - Drupal.t(“текст на английском”);
Переводчиков у нас нет, поэтому программист обязан сделать перевод своего модуля на русский язык сам.
Для этого используется модуль potx.
Potx сканирует указанный модуль и его файлы на предмет наличия вызова функций t(), watchdog(), $t(), format_plural() или Drupal.t(), и помещает найденные строки в новый .po-файл. Учитывайте, что код подобный t($string) или t(‘Order #’ . $text . ‘.’) будет проигнорирован, potx при этом заругается.
Включаем potx, идем в админку (admin/config/regional/translate/extract), ищем свой модуль, отмечаем его; отмечаем внизу “Template file for Русский translations” и “Include translations”. Нажимаем “Extract”, и сохраняем файл как “папка_модуля/translations/*.po”. Редактируя файл, переводим строки на русский и удаляем уже используемые в ядре Drupal строки, оставляя только те, которые использует разработанный модуль. Никаких переводов уже используемых ядром слов типа “Save”, “Settings”, “Edit” и т.п. в этом файле быть не должно, для этого уже есть более глобальные переводы. Тестируем новый перевод либо отключением-деинтсталляцией-инсталляцией своего модуля, либо закачивая файл через админку (http://localhost/admin/config/regional/translate/import)
Если ваш модуль использует какие-либо общеупотребимые в системе слова, но нужно их перевести по другому (например “In progress” было стандартно переведено как “В процессе”), а вам требуется “Комплектуется”, то используйте следующий способ:
в коде модуля - t(‘In progress’, array(), array(‘context’ => ‘sklad’));
в файле перевода -
msgctxt "sklad"
msgid "In progress"
msgstr "Комплектуется"
где произвольно именованный идентификатор “sklad” будет отражать контекст, в котором используется словосочетание “In progress”.
5: Установка и удаление
Модуль в Друпале не просто включается и отключается, ему можно делать инсталл и анинсталл. Используем для этого соответсвенно hook_install() и hook_uninstall(). Первый может использоваться например для создания каких-либо полей или типов материалов, второй - для удаления своих данных из системы, например таблиц или переменных реестра (см variable_get(), variable_set(), variable_del()). Если например ваш модуль свои переменные в БД - реализация uninstall с очисткой системы от своего мусора обязательна!
6: База данных
Если модуль использует свои таблицы БД, то для генерации схемы есть модуль “schema”, который сгенерирует массив по существующей таблице в БД для помещения его в hook_schema() инсталляционного файла. Проектируем и создаем таблицу в БД -> генерируем к ней схему -> копируем/вставляем в инсталл-файл -> чистим кэш (админка “производительность” -> “очистить все кэши”)
7: Обновления модуля.
Если вы внезапно решили (или вам указали, либо возникла иная причина) модифицировать функционал или пришлось изменить структуру данных или именование таблиц/полей/переменных и т.п. , то, что взаимодействует с имеющимися данными - не забываем, что модуль может уже крутиться на десятке сайтов и иметь уже какие-то свои настройки и данные на каждом. Если это так, то hook_update_N() обязателен! Никаких “да я вот переделывал, там надо настройку в админке поправить”. Поправляйте программно, с учетом существующих данных. Если обновление критично (без него на новом коде вылазит fatal error, WSOD или exception) - обеспечиваем (или предупреждаем, запрашиваем у лиц имеющих доступ) немедленноый запуск скрипта обновлений по факту замены модуля на новый.
При написании сложных обновлений, типа конвертации полей или иных перелопачиваний базы после хорошего рефакторинга кода очень легко наступить на грабли. Ситуация: программист написал обновление my_module_update_7013() длиной 150 строк, протестировал у себя локально на тестовом сайте и будучи уверенным в своем коде отправляет это на продакшн. Запускается глобальный скрипт, обновляющий все сайты, и на паре из них вываливается exception. Это значит, что обновление №7013 не прошло, и при следующем запуске апдейтов система вновь предпримет попытку запустить my_module_update_7013(). Но вся проблема в том, что код my_module_update_7013() содержит в себе несколько мощных запросов к БД на запись, где содержатся добавления колонок или их переименования например, и в предыдущей неудачной попытке половина этих запросов уже успешно прошла, прервавшись ексепшном. При повторной попытке ситуация будет уже другой - эксепшены посыпятся уже и по другому поводу (типа, поле уже существует, и т.д.). В результате мы имеем возможно убитый сайт, на который бывает даже невозможно зайти и требующий спешного участия автора апдейта. Поэтому, применяйте свои обновления пошагово, малыми частями. Пишите вместо my_module_update_7013() длиной в 500 строк несколько маленьких - my_module_update_7013() … my_module_update_7033().
8: Ошибки.
На своем dev-сервере в настройках PHP.INI указываем отображение ошибок на полную. В настройках сайта (admin/config/development/logging) ставим галку на “Все сообщения”. Малозначительные нотисы, варнинги - всё на экран, не закрываем на это глаза, хоть это и трудно, но себя перебороть надо. Доводим код до полного отсутствия генерируемых ошибок. Не используем подавление ошибок с помощью “@”.
9: Распространенные ошибки при разработке, недочеты в коде и рекомендации.
-
Двойные кавычки. Старайтесь использовать их только когда это необходимо, ибо http://www.php.net/manual/en/language.types.string.php#language.types.string.syntax.double
-
вместо if (is_array($var) && isset($var[‘abcde’]) && $var[‘abcde’]) {...} достаточно написать if (!empty($var[‘abcde’])) {...} , даже если переменная $var не объявлена, или она пришла не в виде массива, или в массиве $var отстутствует ключ ‘abcde’.
-
вместо array_push($array, $value); достаточно написать $array[] = $value;
-
вместо if ($a > 0) {...} можно написать if ($a) {...} , хотя в случае если $a меньше нуля это не прокатит. Однако в разработке под друпал мы редко работаем с отрицательными числами.
-
вместо if (count($array)) {...} достаточно написать if ($array) {...}
-
вместо if ($x) {$a = ‘yes’;} else {$a = ‘no’;} целесообразнее написать $a = $x ? ‘yes’ : ‘no’;
-
вместо $a == ‘yes’ ? $b = 1 : $b = 0; пишем $b = $a == ‘yes’ ? 1 : 0;
-
вместо $a = $b > 10 ? TRUE : FALSE; пишем $a = $b > 10;
-
многие пишут if (isset($var)) {unset($var);}. Это неправильно. Unset не нуждается в такой проверке.
-
бывает и так: unset($var); $var = array();.
-
C/C++ программисты, опустившиеся до PHP. Отбрасываем привычки навроде приведения типов (int)$var, (string)$str без нужды при каждом пуке (было такое, было). Изучите “Loose comparisons with ==”, “Strict comparisons with ===” и “Comparisons of $x with PHP functions” (гугл в помощь).
-
Не используем алиасы функций вместо актуальных (например, sizeof() = count(), chop() = rtrim())
-
Помните, что вызов функции t(‘text’) влечет за собой сохранение в базе переводов значения ‘text’, если оно там отсутствует, или перевода на русский этого текста, если он есть. Поэтому использование динамических строк типа t(‘count is ’ . $value) недопустимо. Вам придется наблюдать в управлении переводами кучу неперевёденных строк ‘count is 1’, ‘count is 2’, ‘count is 3’, ‘count is 333’, и т.д. Вместо этого пишем t(‘count is @value’, array(‘@value’ => $value));. Изучите справку по t() и format_plural().
-
Вместо if (node_load(123) && $a) {...} и т.п., следует писать if ($a && node_load(123)). Сначала выполняем легкие проверки, а потом тяжелые, не тормозим без нужды работу сайта, drupal и так тормозной!
-
некоторые после использования переменной могут в конце написать unset($переменная), несмотря на то, что далее к ней не будет обращения. Это не очень нужно.
-
switch/case стейтмент не пихаем везде и всюду. Используем разумно.
-
@ - зло.
-
Белый экран иногда (обычно на сабмитах форм), ошибок в коде нет, логи тоже пусты, “не знаю что делать”. - скорее всего закрывающий тэг ?> с переносом или пробелом после, либо PHP-файл в UTF8 с BOM. Кто-то нарушил пункт №0 :) Ищите в последних модифицированных или добавленных файлах.
-
Какие-то внезапные трудно отлавливаемые ошибки в js или стили едут - скорее всего что-то связанное с UTF8 или кириллицей в скриптах. Либо использование для последнего елемента js-массива запятой. Болезненно для ИЕ.
-
Проблема - сайт внезапно стал тормозить. Причины могуть быть разные, но одна из самых неявных - вы по каким-то причинам решили удалить включенный модуль физически, но не отключили его в админке. Ядро в таком случае каждый раз, видя в БД включенный модуль (поле status=1 в таблице system) и не находя его тело, начинает искать по всем папкам. При этом, независимо от результатов поиска система его сама не отключает.
-
Если переменная $var существует, но равна NULL, то isset($var) вернет FALSE.
-
Вместо array_key_exists() проще использовать isset(). Исключение - определение наличия в массиве ключа даже со значением NULL(см выше), тут isset() не сработает.
-
foreach (db_query(‘нехитрый SQL-запрос’) as $result) {...} - это не страшно.
-
запросы (даже нехитрые) в теле foreach/for - вот что страшно.
-
Вместо strlen() используем аналог - drupal_strlen()! То-же самое касается substr(), strtolower(), strtoupper(). См. справку. Исключение - гарантированно не содержащие UTF8 строки.
-
Имена таблиц в SQL-запросах оборачиваем в {плейсхолдеры}. Так-же не используем т.н. грависы (`такие` `вот` `кавычки`)
-
Запросы генерим по обстоятельствам, где-то используем объект db_select(), где-то напрямую вызов db_query(), как проще.
-
При сохранении таймштампа в БД используем константу REQUEST_TIME
-
При выводе этого таймштампа юзеру, используем друпаловскую format_date().
-
Прежде чем изобретать велосипед для игрищ с датами/временем не поленитесь заглянуть в справку по PHP. Там для этого инструментов навалом. Плюс велосипеды в поставке Друпала.
-
То-же самое касается строк и массивов.
-
В функции my_module_install() в установщике модуля, вместо t(‘text’) пишем $t = get_t(); $t(‘text’);
-
В хуке hook_menu() при указании заголовка пункта меню (‘title’ => ‘Title’) не используем ф-ю t().
-
Создал новый пункт меню программно (hook_menu()) - не работает. Нужно чистить кэш Друпала.
-
Так-же - после добавлении каких-либо хуковых функций они не “подхватываются” - чистим кэш Друпала.
-
Добавление всех подряд файлов модуля в директивы files[] = … .info-файла. Если вы увидели такой подход в скачанном с друпал.орг модуле, то это не значит что так и надо делать. Читаем внимательно http://drupal.org/node/542202#files
-
Самое главное - никаких модификаций в файлах ядра или сторонних модулей.
10: Темизация и стилизация
Сначала реализуем логику работы, а только в конце - красивости.
Если стоит задача связанная с javascript, не забываем про “graceful degradation” (плавная деградация) - это когда юзер с отключенным javascript все равно что-то видит, и что-то может сделать. Пример - админка блоков, их можно тягать мышью раскидывая по регионам, но с отключенным яваскрипт нам по прежнему доступны настройки веса и региона. Таким образом, тоже реализуем сначала надежную работу модуля, потом довешиваем javascript.
Если вам дали картинку с синими рюшечками на красном фоне, и чтобы ссылочки были зелененькими - старайтесь сразу не прописывать слепо это в код модуля и его стилей. Это в большинстве случаев решается в пределах текущей темы Друпала. Разделяйте с умом требования для конкретного сайта с конкретным дизайном от общих требований. Самое большое, что можно сделать в модуле - прописать тэгам какие-то свои классы и идентификаторы по необходимости.
Исключение - кастомные модули, разрабатываемые для какого-то конкретного сайта, там воротите что душе угодно.
Кривизну своих стилей рекомендуется так-же потестировать, посмотрев на работу модуля под разными темами.
11: Файлы
Формы администратора и их обработчики выносим в отдельный инклюд типа “my_module.admin(.forms).inc”
То-же самое касается других участков кода, вызываемых далеко не всегда и имеющих большой размер. Память не резиновая.
Смотрим папки модулей в составе ядра, и делаем по подобию, не устраиваем бардак.
Структура файлов модуля средней сложности может выглядеть например так:
/my_module
|- my_module.info
|- my_module.install
|- my_module.module
|- my_module.admin.forms.inc
|- my_module.pages.inc
|- templates
|- my_module.custom_page.tpl.php
|- css
|- my-module.admin.css
|- my-module.some-page.css
|- bg1.png
|- js
|- my-module.some-plugin.js
|- translations
|- my_module.ru.po
Красным отмечены файлы, минимально необходимые системе для включения и нормальной работы модуля.
Жирным отмечены критические имена (или части имен), которые запрашиваются системой автоматически и переименованию не подлежат.
12: Безопасность
-
Вместо db_query(“SELECT * FROM {users} WHERE name = ‘“ . $name . “‘“) пишем db_query(‘SELECT * FROM {users} WHERE name = :name’, array(‘:name’ => $name))
-
Вывод пользовательских текстов в браузер - только через check_plain(), filter_xss(), check_markup() и иже с ними! Даже если этот текст был создан админом.
- Не храним пароли в FTP-менеджере!!!