Разработка

Вступление

Итак, вы хотите создать сайт на базе системы Contenido (далее C7). С чего начать? Как подступиться к этой непростой задаче?

Процесс установки системы мы рассматривать не будем – скорее всего, систему для вас уже поставили (и даже немного настроили). Перейдем сразу к делу.

С7 устанавливается на UNIX-машину от имени специально заведенного пользователя, в его домашнюю директорию (например, /home/user/). Располагайтесь – теперь в этой дирекотрии вам придется бывать часто. Из этой дирекотрии происходит управление проектом (с помощью команд make), в ней вы найдете все файлы, которые понадобятся вам для работы над проектом (не говоря уже о том, что вы и сами насоздаете их тучу). В дальнейшем все относительные пути, которые я буду указывать, будут от этой дирекотрии.

Волшебный путь – src/projects/myproject/comps/www. Через пару дней он будет отскакивать от ваших пальцев за секунду, но проще прописать в .profile алиасы вида:

alias ll='ls -l'
alias la='ls -la'

alias cdh='cd ~/Contenido/'
alias cdl='cd ~/Contenido/src/projects/myproject/lib/myproject/'
alias cdw='cd ~/Contenido/src/projects/myproject/comps/www/'
alias cds='cd ~/Contenido/src/projects/myproject/services/'
alias cdss='cd ~/Contenido/usr/projects/myproject/services/'

и далее не стирать кнопки на клавиатуре.

Вернемся к src/projects/[...]/comps/www. Эта директория есть DocumentRoot для html-страниц, которые составляют web-часть проекта (если быть более точным, для тестовой части проекта, ведь из документации вы узнали, что у любого проекта на С7 есть исходники и рабочая ветка). Именно в ней в самое ближайшее время вам посчастливится создать файл index.html, написать в него какую-нибудь хрень и, торжествуя, обратить взор вашего броузера на тестовый УРЛ проекта. Что ж, какой-никакой, а все-таки первый шаг.

Масон

Как вам уже, видимо, известно, С использует mason в качестве процессора шаблонов (непростого такого процессора, с хорошо развитыми возможностями). Масон оперирует компонентами – файлами, содержащими некий код. Все html-страницы, все компоненты в вашем проекте будут масон-компонентами (html-страницы – те же компоненты, просто у них расширение другое; отличие html от компонент с расширением .msn в том, что *.msn вы не сможете получить по http; кстати, компоненты-html-страницы, которые запрашиваются по http, принято называть компонентами верхнего уровня).

Любая масон-компонента – это текстовый файл, который может содержать специальные масонские (пардон, масоновские) конструкции. При запросе такого файла по http он обрабатывается масоном и пользователю возвращается уже обработанный html (в нашем случае) код.

Лирическое отступление

Вообще говоря, масонская компонента может генерировать не только html и даже не текст – а любой MIME-контент, например, картинки. Надо просто грамотно настраивать content-type

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

Вводная лекция на русском языке

С точки зрения масон каждая компонента – это функция. Именно в виде анонимной sub масон представляет любую компоненту у себя внутри. Все компоненты исполняются в пространстве имен HTML::Mason::Commands (так уж повелось). При выполнении компоненты происходит следующее: обычный текст (не обрабатываемый масоном) накапливается в выходном буфере масона (с тем, чтобы после выполнения компоненты быть отданной на запрос пользователя), но, так как выполнение любой функция может быть прервано с помощью return, у каждой компоненты есть еще и возвращаемое значение. Давайте условимся называть то, что компонента выводит в выходной буфер, «output компоненты».

Поскольку сам масон написан на языке Perl, то и компоненты он принимает исключительно на нем же. Как же добавить perl-код в компоненту? Да очень просто: начните строку с символа ‘%’ – и все, что будет написано далее в этой строке, будет воспринято, как perl. «Ну и чё?» – возможно, спросите вы. Да ничё. Слушайте дальше. Если вам захочется написать много кода (мы на это очень надеемся), то вбивать % в начало каждой строки совсем не обязательно. Достаточно окружить код волшебными конструкциями <%perl> и – и теперь его можно писать, совсем как в обычном скрипте.

% my @a = (1 .. 10);
% push @a, 11;

либо

<%perl>

my @a = (1 .. 10);
push @a, 11;

</%perl>

Естественно, рано или поздно вы захотите (вернее, вам потребуется) выводить значения переменных вашего perl-кода пользователю. Для этого в масоне существует волшебная конструкция <% выражение perl %>, которая может встречаться в немасонском тексте (т.е. тексте, который масон не интерпретирует, как свой).

% my $str = "Петя съел медведя";
А знаете ли вы, что <% $str %>?

Это все прекрасно, но для того, чтобы плодотворно создавать тонны кода и не путаться, необходимо при написании компонент соблюдать некоторые простые правила. Самое, пожалуй, важное из них звучит так: «Отделяйте код от отображения». Ну вот. Только что мы их упорно склеивали, а теперь – отделяйте. Смысл правила таков: сначала происходит вычисление данных, а потом – их отображение, причем в идеальном случае при отображении используется только вставка конечных переменных, конструкции if/unless для условного отображения и конструкции foreach/while для построения списков.

Кроме этого, основной вычисляющий perl-код у нас принято писать в конструкции <%init> ... – это то же самое, что и <%perl> и , с тем лишь отличием что код, помещенный между <%init> ... будет выполнен самым первым делом вне зависимости от того, в каком месте компоненты он встретился. Блок <%init> ... мы обычно помещаем в конец компоненты, после html.

Теперь про аргументы.

Аргументы, которые переданы компоненте (т.е. для html-ки это query_string или данные POST, для остальных рассмотрим ниже) собраны для вас в хэше %ARGS (помсотрите туда, может, там даже что-то есть). Но можно получить более простой доступ к аргументам – с помощью секции <%args>:

<%args>

     $arg1 => undef
     $arg2 => "чево-то там"
     $arg3

</%args>

В приведенном здесь примере в компоненте будут объявлены переменные $arg1, $arg2, $arg3, причем первая получит по умолчанию (если не будет передана) значение undef, вторая – симпатичную строчечку, а третья, если соответствующий ей параметр не будет передан, вообще устроит run time error (поэтому так писать не надо).

Очень важный момент про аргументы.

Поскольку вы уже знаете, как получать аргументы в компоненте и как вставлять их в код страницы, хочу объявить еще одно важное правило: всегда обязательно проверяйте переданные вам аргументы. Иначе всякие умники будут использовать ваши компоненты, как способ нанести ущерб вашему гениальному проекту или простым пользователям интернета, а это нехорошо.

Чего-то я заболтался. Пора нам уже писать нашу первую компоненту, тем более что теории у нас для этого уже более чем достаточно. Итак:

<html>
<head><title><% $title %></title></head>
<body>
% if (@strings) {
<table>

%     foreach my $string (@strings) {
<tr>
<td><% $string->{caption} %>/td;>
<td><% $string->{value} %></td>
</tr>
%     }

</table>
% }
</body>
</html>
<%args>

    $id => undef

</%args>
<%init>

if ($id =~ /\D/) {
    http_abort(404);
}

my @objects; .... действия по извлечению данных, связанных с $id, в массив объектов @objects

my @strings;
foreach my $obj (@objects) {
    push @strings, { caption => $obj->{name}, value => $obj->{value} };
}

</%init>

Видите, как просто. Не бойтесь писать проекты на масоне.

Что же с остальными компонентами? Компоненты – это способ структурирования кода проекта (и html, и perl). Код должен разбиваться на отдельные компоненты по смыслу.

Например, одна компонента выводит шапку сайта. Так как шапка присутствует на всех страницах, имеет смысл написать ее один раз, положить в компоненту comps/header.msn, а во всех страницах писать:

<& comps/header.msn &>

или

<& /www/comps/header.msn &>

Это старый добрый include. Отличие первого варианта от второго очевидно – относительный и абсолютный путь. Относительный от вызывающей компоненты, абсолютный – от корня масонских компонент (в С7 это myproject/comps, поэтому разных Document Root может быть несколько, например, Document Root редакторского интерфейса – core/comps/contenido)

Или же можно вообще всю HTML-обвязку сайта (шапка, футер) включить в некоторую компоненту (пусть это будет /comps/layout.msn):

<& /comps/header.msn &>

<% $m->content() %>

<& /comps/footer.msn &>

, где вызов $m->content() указывает Масону на вычисленное содержимое компоненты верхнего уровня. В самой же компоненте верхнего уровня достаточно сделать вызов:

<&| /comps/layout.msn &>

Здесь находится весь HTML-вывод компоненты верхнего уровня

</&>

и вся обвязка автоматически включится в наш index.html

С вызовом компоненте можно передавать параметры:

<& /www/comps/header.msn, style => "main", title => $title || "Без названия" &>

А еще в масоне есть такие прикольные штуки, как волшебные глобальные объекты. Эти объекты масон создал специально для вас, чтобы вам было удобнее делать многие вещи. Самые интересные (и часто используемые) из них – это объекты $m и $r (понимаете, к чему я клоню? Не вздумайте объявлять переменные с такими именами в своих компонентах! За последствия ответите).

$m – объект-текущая-компонента. Имеет массу полезных методов, подробно описанных в масонской документации, на первых порах вам должны быть интересны методы

$m->comp($comp_name, %args)

и

$m->scomp($comp_name, %args)

это способы вызвать выполнение компоненты прямо из перл-кода (в отличие от <& ... &>). Различие – comp выводит output компоненты как обычно и возвращает значение, которое вернула компонента (с помощью return), а scomp не выводит в выходной буфер ничего и возвращает output вызванной компоненты (как станет видно позже, это позволяет делать массу интересных вещей).

Еще один полезный метод – $m->out($text), который выводит $text в выходной буфер масона, т.е. замена <% ... %> в перл-коде.

$r – объект класса Apache. Точь-в-точь такой же, как и обычно. Со всеми полезностями вроде $r->uri, $r->args, $r->method и т.д.

Последнее, о чем хочу рассказать в вводной лекции на русском языке, это такие великолепные и волшебные компоненты, как autohandler и dhandler (Названия, ясен пень, настраиваемые, эти – по умолчанию).

Смотрите, какое чудо: если в той же директории (или в любой дирекотрии выше вплоть до корня масонских компонент), что и сама вызываемая компонента (правда, это касается только компонент верхнего уровня, например, html-страниц), находится компонента autohandler (какое красивое название), то перед тем, как выполнить вызываемую компоненту, масон выполнит эту самую autohandler. Клёво, правда? Теперь отпадают вопросы типа «а куда бы мне положить инициализацию переменных проекта, которые должны быть доступны на всех страницах?» или «а как мне сделать так, чтобы шапка и попка у всех страниц были одинаковыми, а писать вызовы одинх и тех же компонент по десять раз мне в ломину?». autohandler – вполне себе обычная компонента. Но если не написать в ней вызов $m->call_next, то сама вызываемая компонента так и не будет выполнена – этот метод передаст управление, а после отработки вернет его обратно в autohandler. А вот и пример:

<& comps/begin.msn &>
<%perl>
  $global_var = "GLOBAL VALUE";
  $m->call_next;
</%perl>
<& comps/end.msn &>

А вот и второе чудо – компонента dhandler. Это вообще потрясающая вещь. Допустим, пришел запрос на компоненту верхнего уровня, которая не существует. Ну вот не существует и все. 404, короче. А вот если на одном уровне с ней (или, как водится, повыше, если путь, который указан в ури, существует хотя бы частично; в противном случае – на самом верху) существует волшебная компонента dhandler, то управление будет передано ей. Я называю эту компоненту «урлогенератор», потому как, стоит вам создать ее в какой-либо директории Document Root, как любой URI ниже этого места тут же начинает существовать. Для того, чтобы понять, что же все-таки было вызвано, в dhandler имеет смысл воспользоваться методом $m->dhandler_arg, в который будет записана часть URI от того места, в котором нашелся dhandler. Ниже станет понятно (а особо одаренным понятно уже сейчас), как это чудо можно использовать на практике.

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

Конец вводной лекции на русском языке

Давайте поговорим немного о том, куда еще, кроме компонент, мы можем складывать наш драгоценный код.

Не секрет, что вызов компоненты требует некоего ресурса, ведь файл-компоненту надо открыть, прочитать, скомпилировать (масон-умница умеет кэшировать скомпилированные компоненты, но все же), и, наконец, выполнить. Если в проекте какая-либо компонента вызывается ну очень много раз, да еще к тому же не очень велика размером, и вдобавок ко всему не завязана на особенности отображения (например, что-то вычисляет), то ... да – она не имеет право нести гордое имя масонской компоненты. Она должна немедля стать простой перл-функцией и перекочевать в какой-нибудь package или в специально отведенное для этого место. Специально отведенное для этого место в С7 – это myproject/lib/myproject/Init.pm, модуль, инициализирующий проект, а заодно и включающий в себя сборник всяких полезных функций для проекта (все они будут объявлены в пространстве имен HTML::Mason::Commands, так что обращаться к ним в компонентах можно будет просто по имени).

Конечно же, компоненты – это скорее место для отображения данных, чем для серьезных объемов программного кода. Истинный сэнсей всегда первым делом строит грамотную объектную структуру для проекта, размещает объекты в /lib/myproject/, а потом пишет простые и понятные компоненты, которые используют всю мощь объектно-ориентированного подхода на практике.

Начнем работу с С7

Запуск и останов проекта; прокатка

Постольку поскольку С7 – это отдельный апач-бекенд плюс коннектор к БД, существуют такие понятия как запуск и останов проекта. Для того, чтобы запустить проект, в корне инсталляции необходимо выполнить команду

make start

и, соответственно,

make stop

для останова проекта.

С прокаткой ситуация такая. Прокатке подлежат 2 категории файлов – страницы сайта и программные модули. В девелоперсокй стадии директория со страницами совпадает с «волшебной» директорией (src/projects/...), в которой эти страницы разрабатываются (чтобы при разработке разработчик мог тут же видеть, что же он там наразрабатывал). А вот модули загружаются апачом из отличного от usr/projects/myproject/lib места, поэтому перед перезапуском их необходимо прокатить. Для боевой стадии, естественно, прокатывать надо все. Непосредственно прокатка осуществляется командой

make reload

или, если не надо обращаться к хранилищу версий (репозиторию):

make nano

Структура компонент

Как я уже говорил выше, разбитие кода на компоненты происходит по смыслу. Так всем понятнее. Кроме этого, здорово разделять компоненты по назначению – есть компоненты, которые генерируют данные и ничего не выводят (а только возвращают), есть компоненты, которые только выводят данные (ну может чуть-чуть их подправляя). Мы используем такое деление – компоненты первого типа мы кладем в поддиректорию /comps/subs/, второго типа – в /comps/comps/.

Вообще говоря, хорошая отображающая компонента содержит только отображение, никакого доступа к данным. Хорошая компонента генерации данных содержит в себе обработку кеширования – таким образом, чтобы кеширование запросов к БД было абсолютно прозрачным для вызывающей компоненты. Правда же, вы будете писать только хорошие компоненты первого и второго типов?

Доступ к БД

Как вы уже узнали из документации, С7 связана с базой данных. База эта содержит всякие там секции, которые содержат всякие там документы, которые также могут быть перевязаны всякими там связями, в общем, много чего интересного. Доступ к этой базе данных осуществляется через глобального хранителя данных – волшебный объект $keeper, который заботливо определен создавшими C7 программистами в любой компоненте (глобален в пространстве имен HTML::Mason::Commands, если серьезно).

Самое, самое частое использование этого объекта – это получение документов из базы:

my @rabbits = $keeper->get_documents(
      s => $project->s_alias()->{where_rabbits_live},
      class => ‘Rabbit’,
      status => 1,
      order => [‘date’, ‘direct’],
);

И вуаля – полный массив кроликов. С ненулевым статусом, заметьте.

Подробно о всех возможных параметрах метода get_documents можно почитать в документации, а пока просто посмотрите – простая компонента, отображающая данные из БД, пишется за 5–8 секунд.

Аналогично для получения частей рубрикатора (секций) существует метод $keeper->get_sections:

my @sections = $keeper->get_sections(       s => $root || $project->s_alias()->{somewhere_else},       status => 1, );

Грамотный autohadler

Грамотный autohadler – залог хорошо спроектированного сайта. Как правило, страницы одного сайта имеют между собой много общего. Практически на всех страницах сайтов, хорошо спроектированных с точки зрения usability, присутствует основное меню. Описание этого меню неплохо было бы поместить в autohandler (в виде массива хешей, например). Также практически все страницы имеют одинаковые шапки и футеры – обеспечить эту одинаковость – задача autohandler.

Грамотный dhandler

Одна из главных черт грамотного dhandler – выставление правильного content-type, поскольку сервер, как правило, затрудняется определить тип содержимого несуществующего файла самостоятельно. Сделать это очень просто:

$r->content_type('text/html');

Возврат кода ответа HTTP

Для этого в С7 есть потрясная функция – http_abort($code). Она прерывает генерацию контента страницы, очищает буфер вывода, и возвращает код HTTP-ответа $code.

Построение менюшек

Важный момент при построении менюшек – описание пунктов меню, которое может встречаться в разных частях сайта и даже иногда в разных представлениях, должно находиться в одном месте. Самое лучшее место для описания основного меню сайта – autohandler (или компонента, вызванная из autohandler)

Кэширование данных

Очень важный момент. C7 позиционируется, как система для разработки высоконагрузочных сайтов, требующих от разработчика особого внимания к производительности созданного им кода, к логике работы различных этапов генерации контента в целом. Простой пример: сайт спроектирован хорошо – страница разбита на ряд компонент, каждая из которых отвечает за генерацию отдельного блока страницы – одна за центральную часть, одна за правое меню, и т.д. Допустим также, что есть некий список, который читается из базы данных, и так или иначе должен быть использован в нескольких компонентах.

Поскольку мы говорим о хорошо спроектированном сайте, то за чтение списка из базы также отвечает отдельная компонента (возвращающая данные). Первое, что приходит в голову – вызвать эту компоненту из всех, в которых список потребуется. Это неэффективный способ – даже если компонента данных является кэширующей, мы имеем два вызова одной и той же компоненты на странице. Выход существует такой: в рамках генерации одной страницы использовать кеширование в памяти, используя $request – отличное место, через которое можно протянуть любые данные сквозь весь процесс генерации контента страницы.

Если же речь идет о кешировании в пределах сайта, то любая критичная по времени компонента (а это любая компонента, возвращающая данные из БД), должна иметь «прозрачное» кэширование, настроенное разработчиком. Для этого надо использовать механизм кэширования, встроенный в масон. Типовая схема использования – такая:

<%args>

      $p1
      $p2
      ...
      $pn

</%args>
<%init>

my @data;
my $key = ...; # зависит от p1, p2, ... , pn
my $data_ref = $m->cache->get('data_'.$key, busy_lock => '5 minutes', expire_if => sub { $request->{data}{ARGS}{expire} } );
if (ref $data_ref eq 'ARRAY') {
    @data = @$data_ref;
} else {
    @data = map { {
        id => $_->{id},
        name => $_->{name},
        brief => $_->{brief},
    } } $keeper->get_sections(
        s => ...,
        status => ...,
        ...
    );
    $m->cache->set('data_'.$key => [@data], '5 min');
}
return @data;
</%init>

Работа с датами

Здесь и далее – для некоторых особо часто встречающихся ситуаций существуют готовые компоненты общего употребления, заботливо написанные для вашего удобства разработчиками. Так вот, для преобразования дат из формата, в котором они приходят к нам из БД, в хьюмэнридбл, существует компонента /inc/show_date.msn (не поленитесь открыть ее код для просмотра, там вы найдете инструкцию по ее использованию)

Кроме того, теми же разработчиками придуман специальный модуль Contenido::File, позволяющий получить perl-объект DateTime практически из чего угодно, из палочек и веточек почти что. Описание вызова смотрите в документации.

Грамотное кэширование объектов С7

При попытке восстановить объекты, закэшированные (т.е. сериализованные) mason-ом, происходят всякие недетерминированные ошибки. Причиной тому – сериализация хендлера DBI, который при восстановлении становится чертиком с рожками. Посему повелеваю – перед кешированием объектов С делать следующую операцию:

map { {
p1 => $_->{p1},
p2 => $_->{p2},
....
pn => $_->{pn},
} } @objects;

где p1 ... pn – набор полей, которые жизненно необходимо иметь закэшированному объекту. Все лишнее надо убрать – размеры кеша иногда поражают (да и работает маленький кэш быстрее). Естественно, восстановленые объекты нельзя будет использовать, как объекты (т.е., звать на них методы), хотя и можно сразу после восстановления bless-ануть их в соответсвующий класс, но делать этого не нужно.

Процедуры в компонентах

Плохая идея. Масон преобразует это все в процедуры в процедурах, поэтому можно запросто получить конфликт одноименных процедур в разных компонентах. Хотя в некоторых случаях – можно, например, для реализации рекурсивного алгоритма (использовать для этого <%def> неудобно).

Не совсем так. Вместо процедур можно использовать ссылки: my $myfunc = sub {...}. И тогда никаких проблем не будет. / Dmitry Roslyakov

Работа с картинками

Аналогично работе с датами, все уже практически готово – у любого объекта (документа, секции) есть метод get_image($field_name), который вернет хэш с описанием картинок, которые присоединены к объекту (в поле с именем $field_name).

Построение постраничной листалки

И тут разработчики постарались – /inc/pages_.msn – используйте как есть или возьмите за основу, наверняка потребуется подточить дизайн.

Глобальные переменные

Глобальные переменные уже есть в С7 для ваших нужд. Как известно, программисты очень любят глобальные переменные. В то же время, много глобальных переменных – это плохо. И все программисты это знают. Поэтому в С7 их мало – вот их список:

$state – всякие настройки С-кого проекта
$request – объект-запрос. Существует в течение обработки запроса apache-ом (прям как $r), поэтому это – отличное место для хранения всяких данных, которые генерятся в одной компоненте, а используются в другой.
$project – настройки непосредственно сайта, т.е. в основном всякие дизайнерские штуки
$keeper – объект доступа к БД
$session – переменная для хранения сессии

ФАК

где найти всякие настройки проекта?
настройки проекта находятся в файле config.mk в корневой директории проекта.
как запускать, останавливать проект?
см раздел
мне нужно сделать сервисный скрипт. где его создать? каким условиям он должен удовлетворять?
см раздел
моя супер-компонента возвращает 500 Internal Server Error. Что делать?
В первую очередь посмотреть в error-log (make elog в корне инсталляции). Заметьте – в девелоперской стадии масон будет выводить страницу с описанием ошибки, а в боевой будет показывать 500 ошибку (при обнаружении ошибки в бою смотреть надо уже в боевой лог).
Я хочу захардкодить 3 раза подряд один и тот же массив/хэш. Где мне получить по рукам?
В компании Рамблер, в отделе веб-разработок.

Контрольная работа

Организовать новостную ленту на базе С7. Должна быть возможность добавлять, удалять и редактировать новостные сообщения (новость – это заголовок, анонс, текст, картинки), показ должен происходить постранично (на одной странице – N, а еще лучше – M новостей), причем если у новости есть картинка, то рядом с анонсом должна быть ее превьюшка + должна быть кнопка сортировки (хронологический порядок/обратный).

Запишите текущую дату в формате «28 января 2005» в переменную $date наикратчайшим образом.

Напишите нам

Организация запросов к БД
Начальная структура данных проекта
Объектная модель данных. Интеграция таблиц в Contenido
Стандарты разработки
Стандарты оформления