48 советов для программистов от Lullabot [перевод]

Главные вкладки

Аватар пользователя seaji seaji 2 марта 2010 в 14:52

48 Essential Drupal Development Tips From Lullabot

Оригинал статьи
http://www.missingfeatures.com/2010/02/16/48-essential-drupal-developmen...

От переводчика:
Автор статьи принимал участие в 4-х дневном курсе обучения Drupal от Lullabot по таким направлениям как: темизация, API форм, API меню, разработка модулей, jQuery и лучшие практики.

Все советы от Lullabot были аккуратно записаны и опубликованы в данной статье. От себя замечу, что некоторые советы даже для меня, матерого программиста, были в новинку.

UPD: для хабралюдей: http://habrahabr.ru/blogs/drupal/86073/

1. Выводите переменную $body_classes в атрибут "class" тега "body" в вашем файле page.tpl.php для того, что бы иметь возможность использовать различные классы страничек, например “front” - главная, “not_front” - не главная, “logged-in” - для залогиненных и пр.

2. Страничка "/admin/build/block" это единственная админская страница, на которой не используется администраторская тема оформления. Это сделано для того, что бы вы имели представление о местонахождении своих блоков на сайте.

3. Чаще всего в шаблон page.tpl.php забывают вставлять переменные $closure и $tabs.

4. Наиболее распространенным методом темизации является копирование файлов шаблонов из модулей или тем оформления и редактирования их под свои потребности.

5. Для решения проблем с различным порядком слов в различных языках используйте плейхолдеры. Например:$variables['submitted'] = t('On date', array('date'=>format_date($variables['created'],'custom','F jS')));

6. [Лучшая практика] В случае обновления системы лучшей практикой является стирание и создание заново тех темизирующих функций, которые вы переопределяете. Таким образом вы можете включить любые изменения кода, которые вы не захватили.

7. Если вы не хотите использовать переменную $content в шаблоне node.tpl.php, то не используйте. Вы можете выводить различные поля отдельно друг от друга.

8. [Производительность] Во вьюс предпочитайте использовать стиль ряда "Fields" (поля), а не "Nodes" (ноды). В режиме "Nodes" происходит вызов node_load() на каждой ноде в вашем виде, что может стоить вам более 50 запросов на каждую ноду. В режиме "Fields" происходит просто сбор необходимой информации в одном запросе.

9. Обратите внимание на функцию dsm() - это что то типа функции pr() в CakePHP (http://cakephp.org/). Эта функция может выводить на страницу сложные структуры данных в удобном для восприятия виде. Требуется модуль devel (http://drupal.org/project/devel).

10. Группируйте кастомные модули в одной и той же "группе" (“package”) и тогда Вам не придётся рыскать по странице со списком модулей в поисках того или иного модуля.

11. Для отслеживания изменений в API между D6 и D7 используйте модуль coder (http://drupal.org/project/coder)

12. [Лучшая практика] Переменная $user относится к пользователю, который залогинен на сайте в момент исполнения кода, переменная $account относится к любому пользователю вне зависимости от того, залогинен он на сайте или нет.

13. [Производительность] На каждой странице происходит загрузка всей таблицы переменных (variable), поэтому имеет смысл следить за этой таблицей и удалять ненужные значения.

14. [Производительность] Функция variable_get() не вызывает запросов к базе данных т.к. все переменные хранятся в памяти в момент исполнения кода.

15. Не вызывайте функцию t() для названий и описаний пунктов меню. Они сохраняются в кеше в момент билда меню. Это значит, что язык, установленный в момент создания кеша меню будет использоваться для всех пользователей.

16. Используйте MENU_LOCAL_TASK для пунктов меню, которые Вы хотите сделать табами. Например, таким образом можно добавить табы для таких страниц как "node/*" или "user/*"

17.
[Производительность] Используйте для Ваших коллбэк функций из меню отдельные файлы *.inc с помощью задания аттрибута "file" в массиве, определяющем элемент меню. Это приведет к более эффективному распределению памяти т.к. файл модуля *.module загружается на каждой странице, а заданные файлы *.inc загружаются только при вызове данного пункта меню.

18. При использовании %user или %node в определении пункта меню Drupal автоматически запускает функции node_load() или user_load() передавая этим функциям в качестве аргумента то, что стоит на их месте в url (обычно это ID) и возвращают объект - ноду или объект - пользователя

19. Вы можете определить свой собственный хендлер % в определении меню. Например, %example будет вызывать Вашу функцию example_load(). Эта функция должна находиться в главном файле модуля *.module а не в файлах *.inc

20. В своем модуле Вы можете использовать $GLOBALS['conf']['cache'] = false . В этом случае будет отключено кеширование данной страници. Обратите внимание на то, что если эта страница была закеширована ранее, то потребуется скинуть кеш.

21. Вы без труда определите, что данный сайт работает на Drupal если посмотрите в заголовках "page expire date" дату устаревания страницы. У Drupal это 11/19/1978 - дата рождения создателя Drupal. (http://drupal.org/user/1)

22. Использовать "маршрутизацию" (перенаправление) в Drupal можно с помощью функций custom_url_rewrite_inbound() и custom_url_rewrite_outbound()

23. К вопросу выбора правильного типа поля "дата" в CCK:
- Дата (ISO) - хорошо подходит для не точных дат (например, только год)
- "Datestamp" ( штамп даты, кол. секунд с момента начала эры Unix) - тот же формат используется в ядре Drupal
- Лучше всего использовать "Datetime", этот формат имеет ту же точность что и "Datestamp". Кроме того, "Datetime" сохраняет данные в родном формате базы данных, что позволяет проводить различные манипуляции прямо на уровне базы данных (что очень быстро).

24. [Лучшая практика] Разбивайте Вашу папку "sites/all/modules" на две дополнительные папки "contrib" и "custom", для скачанных и своих модулей соответственно.

25. [Лучшая практика] Если Вам необходимо модифицировать код чужих модулей, то отслеживайте все изменения и сохраняйте их в специальных файлах патчей. Создайте отдельную директорию для сохранения всех файлов патчей. Каждый раз, когда Вы обновляете модуль, удостоверьтесь, что Ваш патч все ещё необходим. Примените совой патч к новой версии модуля (если это требуется), либо удалите патч из Вашей папки (если патч уже включен в модуль).

26. [Лучшая практика] Хук hook_menu() должен быть первой функцией в Вашем модуле, потому что он играет роль "индекса" для модуля, который описывает что делает Ваш модуль и когда он это делает. (Имеется ввиду не программная сторона, а девелоперская. Любой разработчик глядя на Ваш hook_menu() может легко начать ориентироваться в Вашем модуле).

27. Причина по которой аттрибуты форм начинаются со знака "#" в массиве $form заключается в возможности создавать вложенности форм в массиве.

28. Атрибут "clicked_button" используется для управления сабмитом кнопок, которые являются картинками. Т.к. интернет эксплорер не использует при сабмите имена кнопок, как это делают другие браузеры.

29-30. Вы обычно устанавливаете ошибку в поле во вложенной форме используя формат parent][child (Например "home][street")? А Вы знаете, что form_error() позволяет устанавливать ошибку в поле с использованием более логичного форматирования чем form_set_error()
form_set_error('home][street','You must enter the street address.');
form_error($form['home']['city'], ‘You must enter the street address.’);

31. Если в переменной $form_storage находится хотя бы что то, в этом случае Drupal будет игнорировать любые редиректы и будет просто делать ребилд формы при сабмите. Что бы избежать этого, в некоторых случаях придется делать unset $form_storage.

32. [Лучшая практика] Вы можете использовать любой HTML код в Ваших функциях темизации потому, что система темизации построена таким образом, что она может переопределять HTML в своих собственных функциях.

33. Drupal будет автоматически рендерить любые неотрендеренные части массива $form. Поэтому Вам совсем не обязательно рендерить вручную каждую часть. Только если вы хотите управлять этой частью индивидуально и отдельно от остальных.

34. Для быстрого переключения между базами данных используйте db_set_active(). Эта функция позволяет переключаться между соединениями с базами, которые Вы указали в своем файле settings.php.

35. Модуль Table Wizard (http://drupal.org/project/tw) позволяет создать описание любой таблицы для views. Вы даже можете выбрать ключи таблицы, которые используются для джоинтов.

36. Если Вы установите для элемента формы атрибут "#value" (например, для скрытого поля), то Drupal при обработке сабмита всегда будет устанавливать значение этого поля в то, что Вы укажете в "#value" независимо от того, что будет получено от пользователя т.к. при сабмите происходит ребилд формы, а уже затем обработка.

37. Если тип элемента формы ("#type") это "value", то этот элемент формы не будет включен в HTML форму на пользовательской стороне. Пользователь вообще не увидит этих данных, но в обработчиках формы в Drupal эти данные будут доступны на ряду с другими данными формы.
Например: $form['element_name'] = array("#type" => "value", "#value" => "My Value");
My Value - будет доступен в $form_state['values']['element_name'], но не будет присутствовать на страничке с формой.

38. [HTML] Если в страницу включен JavaScript (инлайн, прямо в тексте страницы), то в этом месте будет происходить полная остановка загрузки всего, что идет после этого скрипта (HTML, JavaScript и др.) до тех пор, пока код не загрузится.

39. [JQuery] VisualjQuery.com это удобное, визуальное API к JQuery

40. [Firebug] можно запустить JavaScript прямо в консольном табе Firebug если использовать ">>>" который находится внизу консоли.

41. [HTML] Некоторые браузеры будут вырезать теги "A" если для них не определен атрубут "href".

42. [Производительность JQuery] Если указать тег вместе с классом, то поиск этого элемента будет происходить намного быстрее чем если указать просто класс.
Например:
Медленно: $('.content');
Быстро: $('div.content');

43. [Производительность JQuery] Использование $(this) внутри функций селекторов дает большую скорость чем использование селектора еще раз заново.

44. Самым удобным способом управления вьюсами является экспортирование необходимого вьюса и сохранение его в своем модуле. Это приводит к тому, что вы можете следить за изменением вида с помощью систем контроля версий. Еще ваш вид становится защищенным от случайных ошибок пользователей, Вы всегда можете сделать "revert" и возвратиться к исходному состоянию.

45. Управление патчами: Создайте пустой модуль и используйте хук hook_update() для того, что бы вносить в сайт большие изменения, например, в настройках и пр. Таким способом можно, например, после обновления модуля еще запустить update.php и накатить на новый модуль Ваш патч.

46. Безопасность данных: По большому счету, на уровне темизации все данные должны быть уже безопастны. Что бы обезопасить свой сайт от данных, введенных пользователем используйте check_plain (все теги вырезаются) или check_markup (текст проходит обработку фильтром ввода поумолчанию).

47. Вы можете создать drush_make скрипт для Drush (http://drupal.org/project/drush), который будет создавать чистую установку Drupal со свежими версиями модулей, которые будут скачаны с Drupal.org прямо в момент установки.

48. Как можно больше используйте функции cache_get() и cache_set() т.к. они уменьшат количество запросов к базе данных.

Из комментариев

Советы по JQuery:

1. Если только возможно, то используйте ID для селекторов jquery.
$(‘#block-menu-1") это очень быстро, примерно в 100 раз быстрее чем $(‘div.block’)
а $(‘div.block’) всего лишь немного быстрее чем $(‘.block’)

2. Кешируйте данные.
Плохо:
$(‘#wombat’).hide();
$(‘#wombat’).remove();

Хорошо:
var wombat = $(‘#wombat’);
wombat.hide();
wombat.remove():

Отлично:
$(‘#wombat’)
.hide()
.remove();

3. Вы можете передать переменную в Firebug с использованием console.log()
var lemmeSee = $(‘#wombat’);
console.log(lemmeSee)

Другие полезные функции модуля Devel

dpm() – впечатывает объект в сообщение.

dargs($once = TRUE) – печатает аргументы текущей фукнции, $once = TRUE в этом случае печатает их только один раз.

dd() – записывает объект в файл /tmp/drupal_debug.txt – очень полезно, когда нужно сделать дебаг Ajax запросов. если запросы идут из Flash плеера, то у них другой ID сессии. Можно так же использовать watchdog(‘debug’, print_r($object, TRUE)) , в этом случае объект будет записан в базу данных.

dfb() – выводит информацию в firebug (нужно установить FirePHP в модуле Devel)

Как узнать какие переменные доступны в файлах шаблонов:

Это сделать очень легко вызвав dpm(get_defined_vars())
или var_dump(get_defined_vars())

Еще немного советов:

Вы можете без труда создать схему базы данных для Вашего модуля по следующему алгоритму:

1. Создайте необходимую таблицу (или таблицы) в Вашем любимом инструменте (например, в phpMyAdmin)
2. Экспортируйте таблицу с помощью модуля Schema (http://drupal.org/project/schema). Вы можете просто скопировать полученный массив в Ваш модуль, в хук hook_schema

Вы можете экспортировать Ваш тип контента (CCK) с использованием модуля "CCK Content Copy" (который входит в пакет CCK) и вставить этот код к инсталяционный хук Вашего модуля.

Комментарии

Аватар пользователя Виктор Степаньков ака RxB Виктор Степаньк... 2 марта 2010 в 15:03

"seaji" wrote:
31. Если в переменной $form_storage находится хотя бы что то, в этом случае Drupal будет игнорировать любые редиректы и будет просто делать ребилд формы при сабмите. Что бы избежать этого, в некоторых случаях придется делать unset $form_storage.

Думается мне, что имелось ввиду $form_state['storage']

Аватар пользователя alexandr.poddubsky alexandr.poddubsky 2 марта 2010 в 15:34

Server nginx/0.8.33
Date Tue, 02 Mar 2010 12:27:00 GMT
Last-Modified Mon, 22 Feb 2010 22:20:20 GMT
Connection keep-alive
Keep-Alive timeout=20
Expires Thu, 31 Dec 2037 23:55:55 GMT
Cache-Control max-age=315360000

где этот page expire --не нашел

Аватар пользователя seaji seaji 2 марта 2010 в 15:35

"RxB" wrote:
Думается мне, что имелось ввиду $form_state['storage']

Да, уж точно Smile автор ошибся, а я не поправил, но все поняли что имелось ввиду Smile

А вообще, немного сумбурный получился списочек, как будто информация выхвачена то тут, то там.
Я такой стиль не очень люблю, но нельзя не согласиться что много полезного есть в этой куче.

Аватар пользователя seaji seaji 2 марта 2010 в 16:04

Поражаюсь я Хабру. Уже два минуса в карму схватил. Теперь не могу писать в тематический блог.
Если только смогу выбраться из этой ямы, то буду делать только закрытые топики, только для подписчиков блога.

Аватар пользователя Valeratal Valeratal 2 марта 2010 в 17:26

таблицы переменных (variable), поэтому имеет смысл следить за этой таблицей и удалять ненужные значения.

а что там есть , и как понять что там удалить ненужное?

p.s. На хабре, за топик проголосовал, за карму пока не могу, у самого небольшая

Аватар пользователя Виктор Степаньков ака RxB Виктор Степаньк... 2 марта 2010 в 17:14

Некоторые модули не подчищают при удалении переменные за собой. Как правило, их можно выцепить по префиксу, если программист кривого модуля следовал правилам кодирования под друпал, естественно

Аватар пользователя alexandr.poddubsky alexandr.poddubsky 3 марта 2010 в 0:08

function drupal_page_header() {
header("Expires: Sun, 19 Nov 1978 05:00:00 GMT");
header("Last-Modified: ". gmdate("D, d M Y H:i:s") ." GMT");
header("Cache-Control: store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", FALSE);
}

// The following headers force validation of cache:
header("Expires: Sun, 19 Nov 1978 05:00:00 GMT");

Аватар пользователя seaji seaji 3 марта 2010 в 0:24

"Valeratal" wrote:
чтобы удалить что-то ненужное, нужно сначало поставить что-нибудь ненужное :)

Вовсе не обязательно.
Даже само ядро может Вам туда напихать много ненужного именно Вам. Не говоря уже про дополнительные модули Smile

Аватар пользователя Nikit Nikit 3 марта 2010 в 5:25

Структурности нет, тяжело будет что-то искать.

26. [Лучшая практика] Хук hook_menu() должен быть первой функцией в Вашем модуле,

по идее правильный совет, но даже у коре-модулей всё в разнобой Smile


8. [Производительность] Во вьюс предпочитайте использовать стиль ряда "Fields" (поля), а не "Nodes" (ноды).

заново рисовать тизеры нод с полей во вьюс? да ну.

Аватар пользователя alexandr.poddubsky alexandr.poddubsky 3 марта 2010 в 6:10

"Nikit" wrote:
8. [Производительность] Во вьюс предпочитайте использовать стиль ряда "Fields" (поля), а не "Nodes" (ноды).

заново рисовать тизеры нод с полей во вьюс? да ну.

ну то что node load гребет немерянно то это да верно

Аватар пользователя orangeudav orangeudav 3 марта 2010 в 14:55

Насчет распихивания кода по отдельным inc-файлам: сейчас 2010 год, и использовать php без включенного кешера (APC, eAccelerator итд) - это быть самому себе (и серверу) врагом. Так что, тут скорее эстетическй подход.

Аватар пользователя seaji seaji 3 марта 2010 в 16:09

"orangeudav" wrote:
сейчас 2010 год

Да, представляете, а на всех шаред-хостингах ни каких акселераторов недопросишься.
Я уже пытался выпрашивать, они говорят, что пробовали ставить акселератор, но из за него у некоторых пользователей возникли проблемы и они его включать больше не собираются.

Аватар пользователя orangeudav orangeudav 3 марта 2010 в 16:57

seaji wrote:
"orangeudav" wrote:
сейчас 2010 год

Да, представляете, а на всех шаред-хостингах ни каких акселераторов недопросишься.
Я уже пытался выпрашивать, они говорят, что пробовали ставить акселератор, но из за него у некоторых пользователей возникли проблемы и они его включать больше не собираются.

бегите от них, они идиоты.
включенный опкешер -> больше клиентов на одном серваке -> больше денег. а им наверное деньги не нужны..

Аватар пользователя Dеmimurych Dеmimurych 3 марта 2010 в 21:14

"orangeudav" wrote:
Насчет распихивания кода по отдельным inc-файлам: сейчас 2010 год, и использовать php без включенного кешера (APC, eAccelerator итд) - это быть самому себе (и серверу) врагом. Так что, тут скорее эстетическй подход.

который к тому же, еще при включенном (APC, eAccelerator итд) вреден.

Quote:

42. [Производительность JQuery] Если указать тег вместе с классом, то поиск этого элемента будет происходить намного быстрее чем если указать просто класс.
Например:
Медленно: $('.content');
Быстро: $('div.content');

не всегда верно.
можно сверстать страницу таким образом, что div.content будет сравнительно таким же медленным как и .content

суть совета в том, чтобы максимально ограничивать области поиска нужного селектора.
например заданием контекста

http://designformasters.info/posts/speed-up-jquery/

Аватар пользователя orangeudav orangeudav 3 марта 2010 в 21:19

Dеmimurych wrote:

не всегда верно.
можно сверстать страницу таким образом, что div.content будет сравнительно таким же медленным как и .content

суть совета в том, чтобы максимально ограничивать области поиска нужного селектора.
например заданием контекста

http://designformasters.info/posts/speed-up-jquery/[/quote]

если обобщить - для выборки по id jQuery использует нативную функцию браузера, для выборки по классу - перебор. Так что надо как можно точнее ограничивать область при помощи id, а далее уточнять классами

Аватар пользователя seaji seaji 4 марта 2010 в 1:21

"Dеmimurych" wrote:
можно сверстать страницу таким образом, что div.content будет сравнительно таким же медленным как и .content

В исходной статье говорится, что "browsers have built-in support for getElementysByTagName()"
Т.е. браузеры имеют встроенную поддержку getElementysByTagName() именно поэтому div.content быстрее.

Аватар пользователя Dеmimurych Dеmimurych 5 марта 2010 в 12:01

"seaji" wrote:
В исходной статье говорится, что "browsers have built-in support for getElementysByTagName()"
Т.е. браузеры имеют встроенную поддержку getElementysByTagName() именно поэтому div.content быстрее.

это заблуждение.

getElementysByTagName()
Summary
Returns a list of elements with the given tag name. The subtree underneath the specified element is searched, excluding the element itself.

никакой привязки к классу в этой функции нет.
иными словами все равно придется в цикле перебрать все DIV на поиск соответствующего класса.

Иными словами сверстав страницу с тысячей div и применив к ней $('div.content') мы получим тоже самое время выполнения как и выполнив $('.contennt') И там и там переберется в цикле почти равное количество элементов.

этот совет вводит в заблуждение, не давая нормального понимания того КАК нужно выстраивать селекторы в jQuery, при желании получить производительный код.

Аватар пользователя Anton L. Safin Anton L. Safin 10 марта 2010 в 9:45

Очень полезно! До многого доходил своим умом, но некоторые советы очень пригодились (или пригодятся в дальнейшем). Спасибо.

Аватар пользователя Dan Dan 10 марта 2010 в 13:05

"seaji" wrote:
Все советы от Lullabot были аккуратно записаны и опубликованы в данной статье. От себя замечу, что некоторые советы даже для меня, матерого программиста, были в новинку.

Матёрый программист должен знать все эти пункты. Самооценка у парня хорошая.

"seaji" wrote:
9. Обратите внимание на функцию dsm() - это что то типа функции pr() в CakePHP (http://cakephp.org/). Эта функция может выводить на страницу сложные структуры данных в удобном для восприятия виде. Требуется модуль devel (http://drupal.org/project/devel).

Почему никто не говорит про krumo()? Имхо гораздо удобнее.

"seaji" wrote:
12. [Лучшая практика] Переменная $user относится к пользователю, который залогинен на сайте в момент исполнения кода, переменная $account относится к любому пользователю вне зависимости от того, залогинен он на сайте или нет.

Странная фраза. Обе переменные надо ещё определить, а потом они уже будут что-то обозначать.

"seaji" wrote:
21. Вы без труда определите, что данный сайт работает на Drupal если посмотрите в заголовках "page expire date" дату устаревания страницы. У Drupal это 11/19/1978 - дата рождения создателя Drupal. (http://drupal.org/user/1)

Прикольно, не знал. Ещё один способ на определения друпала Smile

"seaji" wrote:
24. [Лучшая практика] Разбивайте Вашу папку "sites/all/modules" на две дополнительные папки "contrib" и "custom", для скачанных и своих модулей соответственно.

Корректнее сказать, что можно создавать свои папки и они будут просмотрены рекурсивно. А какие это папки - сами решайте (например features можно добавить в этот список.)

"seaji" wrote:
29-30. Вы обычно устанавливаете ошибку в поле во вложенной форме используя формат parent][child (Например "home][street")? А Вы знаете, что form_error() позволяет устанавливать ошибку в поле с использованием более логичного форматирования чем form_set_error()

form_set_error('home][street','You must enter the street address.');

form_error($form['home']['city'], ‘You must enter the street address.’);


Вот это не знал, гуд.

"seaji" wrote:
35. Модуль Table Wizard (http://drupal.org/project/tw) позволяет создать описание любой таблицы для views. Вы даже можете выбрать ключи таблицы, которые используются для джоинтов.

Надо ещё упомянуть модуль Date - он является надстройкой над Table Wizard и Sheme.

"seaji" wrote:
44. Самым удобным способом управления вьюсами является экспортирование необходимого вьюса и сохранение его в своем модуле. Это приводит к тому, что вы можете следить за изменением вида с помощью систем контроля версий. Еще ваш вид становится защищенным от случайных ошибок пользователей, Вы всегда можете сделать "revert" и возвратиться к исходному состоянию.

Features ещё удобнее.

"seaji" wrote:
45. Управление патчами: Создайте пустой модуль и используйте хук hook_update() для того, что бы вносить в сайт большие изменения, например, в настройках и пр. Таким способом можно, например, после обновления модуля еще запустить update.php и накатить на новый модуль Ваш патч.

Интересное решение, надо будет попробовать.

Аватар пользователя orangeudav orangeudav 10 марта 2010 в 13:13

"Dan" wrote:
Почему никто не говорит про krumo()? Имхо гораздо удобнее.

я не заметил особой разницы между krumo и dsm. К тому же, периодически не получается вызвать из krumo из view-view--field.php-файла — спасает dsm

Аватар пользователя Dan Dan 10 марта 2010 в 16:18

"orangeudav" wrote:
я не заметил особой разницы между krumo и dsm

Особой разницы и нет - dsm - обёртка, но занимает больше места на странице, имхо.

"orangeudav" wrote:
что есть Features ?

[module=features], см. ссылки под Learn more.