Действие

Материал из Eludia
Перейти к: навигация, поиск

Содержание

Действие ($_REQUEST {action}) — это символическое имя определённой операции преобразования данных в рамках заданного типа.

Возможно, вам приходилось иметь дело с WEB-приложениями, где одна "активная страница", сначала создаёт запись в БД (например, новый документ) или изменяет её содержимое (например, увеличивает денежную сумму), а потом тут же отображает её. Тогда вам должны быть хорошо знакомы эмоции клиента, которого угораздило нажать кнопку "Обновить" на такой странице.

Чтобы раз и навсегда гарантировать себя от подобных неприятностей, все, абсолютно все изменения в БД в WEB-интерфейсах (не только в Eludia.pm, а во всех) следует производить в 2 приёма:

  • модифицировать данные (INSERT / UPDATE / DELETE и им подобными SQL-запросами; файловыми операцями);
  • формировать ответ-перенаправление (redirect) на URL страницы показа данных.

Стадия проверки и подготовки данных традиционно выносилась в отдельную процедуру, хотя в настоящее время данная схема считается несколько устаревшей (вместо return из validate... удобнее использовать die из-под do_...). Во всяком случае, порядок работы следующий:

  • сначала вызывается процедура
validate_$_REQUEST{action}_$_REQUEST{type}

Если она определена и вернула непустое значение, то это значение выдаётся клиенту в качестве сообщения об ошибке, на чём обработка запроса завершается;

  • если ошибки не произошло, то вызывается процедура
do_$_REQUEST{action}_$_REQUEST{type}
  • если текущее действие — не create, то затем вызывается процедура
recalculate_$_REQUEST{type}
  • если по ходу исполнения возникла ошибка (die), то клиенту высылается javaScript, приводящий к отображению соответствующего сообщения;
  • если же ошибок нет и переменная $_REQUEST {__response_sent} пуста (в частности, ни разу не вызывалась функция redirect), то handler автоматически перенаправляет клиента на экран, чей URL наследует от текущего состояния %_REQUEST значения всех параметров, кроме action и тех, чьи имена начинаются на '_'. Исключение составляет случай action=delete, когда перенаправление обязательно идёт на вызывающую страницу (esc);
  • при непустой $_REQUEST {__response_sent} система считает, что вызванные процедуры уже сформировали ответ при помощи API WEB-сервера, так что обработка запроса прекращается.

Все упомянутые процедуры должны быть определены в файле Content/$_REQUEST{type}.pm.

Рассмотрим простой пример. Допустим, нам необходимо реализовать изменение поля label записи таблицы users (имени пользователя). Редактирующий URL должен иметь вид

/?type=users&action=update&id=$_REQUEST{id}&_label=$label 

DML-запрос UPDATE users ... следует разместить в процедуре do_update_users, определённой в файле Content/users.pm. После исполнения данной процедуры клиент будет перенапрвавляться на адрес

/?type=users&id=$_REQUEST{id} 

Данный URL соответствует карточке пользователя с номером $_REQUEST {id}.

Такая зависимость между редактирующим и следующим за ним презентационным запросом – это то, что на практике нужно в 95% случаев. В остальных 5% можно поменять содержимое %_REQUEST либо воспользоваться процедурой redirect или же, если вам требуется возврат на вызывающий экран, то её специальным вариантом: esc.

Отображение ошибок

Используя функцию die, необходимо помнить, что Eludia.pm использует модуль Carp для того, чтобы сообщения сопровождались полными stack trace.

В случае фатальных ошибок типа недоступности БД это весьма ценная информация, однако для ошибок проверки данных (типа обязательности и уникальности значения поля) она, очевидно, мешает.

Для того, чтобы исключить приписывание stack trace, необходимо указать в начале сообщения об ошибке имя поля, к которому оно относится — в том же формате, в котором оно традиционно присутствовало в сообщениях валидаторов:

$_USER -> {role} eq 'admin' or die '#_foo#:А вы ведь вовсе не админ...';

HTML-поле не обязано существовать, в данном случае префикс с решётками — это просто способ показать нефатальность ошибки.

Стандартные действия

Каждый разработчик волен выбирать имена действий по своему усмотрению, но, как и везде в Eludia, в сатндартных ситуациях мы настоятельно рекомендуем пользоваться стандартными именами действий. Если программист следует этой дисциплине, то помимо унификации кода он получает довольно весомый бонус: в большинстве случаев необходимость писать и отлаживать код отпадает вовсе. Дело в том, что, не найдя процедуры do_$_REQUEST{action}_$_REQUEST{type}, обработчик проверяет наличие do_$_REQUEST{action}_DEFAULT и если таковая определена, то исполняет её. Для нижеописанных стандартных действий DEFAULT-процедуры определены в ядре и как правило делают именно то, что предполагается.

Возможно вам станет легче на душе, если вы будете мыслить в терминах "класс $_REQUEST{type} является наследником абстрактного класса DEFAULT, воплощая интерфейс и т. д." – ну пожалуйста, ради-бога.

create

Так должно называться действие, создающее новую запись в таблице. Имеется жёсткое ограничение: имя таблицы должно совпадать с $_REQUEST{type}, так как именно при этом действии для данной таблицы осуществляется сборка мусора (fake-записей). Если Вы во что бы то ни стало желаете порождать записи на экране, отличающемся по имени от таблицы, можете выбрать другое имя действия (скажем, create_new). В этом случае вы берёте на себя ответственность за сборку мусора: для этого нужно использовать процедуру delete_fakes.

В большинстве случаев процедура do_create_$_REQUEST{type} создаёт новую fake-запись в таблице $_REQUEST{type} и запоминает её номер в $_REQUEST{id} – так что следующим запросом пользователь получает экран редактирования новой записи.

В do_create_DEFAULT значения всех параметров запроса, совпадающих по именам со столбцами таблицы, присваиваются соответствующим полям новой записи. Этим удобно пользоваться, например, для передачи ссылки на родительскую запись у дочерних объектов.

href => "/?type=articles&action=create&id_rubric=$data->{id}",

Впрочем, желательно добавлять к именам параметров префикс '_', при этом они также будут использоваться, однако не станут автоматически наследоваться путём URL rewriting:

href => "/?type=articles&action=create&_id_rubric=$data->{id}",

Создание дочерних объектов

Если модель данных такова, что у каждой вновь создаваемой записи текущей таблицы всегда есть ссылка на родительскую запись в другой (однозначно определённой) таблице, то удобнее всего и одновременно безопаснее вовсе не указывать эту ссылку в URL. В приведённом примере достаточно прописать в Model/articles.pm

id_rubric => {TYPE_NAME => 'int', parent => 1},

и do_create_DEFAULT автоматически определит id_rubric по Esc-адресу.

Но и это ещё не всё. При использовании опции parent в Model do_create_DEFAULT копирует из родительской записи в дочернюю значения всех ссылочных (то есть начинающихся на 'id_') полей. Например, если в указанном примере и Model/articles.pm, и Model/rubrics.pm содержат описание поля

id_topic => {TYPE_NAME => 'int'},

то при создании новой записи articles туда будет скопировано значение id_topic из родительской записи rubrics. Точнее, будет установлено значение по умолчанию для $_REQUEST {_id_topic}. Если вы хотите переопределить значение такого параметра, сохранив функциональность do_create_DEFAULT в целом, это можно сделать в процедуре-валидаторе. Например, вот так будут создаваться статьи с заведомо пустыми темами:

sub validate_create_articles {
  $_REQUEST {_id_topic} = undef;
  undef;
}

update

Так должно называться действие, обновляющее значения полей таблицы данными, введёнными в экранную форму. Имена параметров при этом, как правило, получаются из имён полей таблицы приписыванием префикса '_'. Это необходимо для того, чтобы введённые данные не наследовались в последующих запросах. Приписывание подчерков осуществляется автоматически при генерации HTML-кода формы процедурой draw_form, как и подстановка по умолчанию значения update в скрытое поле action.

Соответственно, процедура do_update_DEFAULT обновляет значения всех полей таблицы $_REQUEST{type}, для которых в запросе указан подчёркнутый параметр. Неотмеченные поля типа checkbox не упоминаются в запросе, однако в $_REQUEST они попадают в любом случае, поскольку их список передаётся в качестве специального параметра (__form_checkboxes). Группы галочек (checkboxes / tree) обрабатываются процедурой sql_store_ids.

Если на форме имеется поле $file типа file, а в таблице определены поля ${file}_name, ${file}_type, ${file}_size и ${file}_path, то вызывается функция sql_upload_file. Более подробно о работе с файлами см. ниже.

do_update_DEFAULT обнуляет значение поля fake и оставляет без изменения $_REQUEST{type} и $_REQUEST{id}. Как правило, это именно то, что нужно. Однако, если вы реализуете, например, многошаговый мастер создания объекта, эта логика не подходит: поле fake должно оставаться без изменений, а $_REQUEST{type} – меняться на тип экрана для следующего шага. Несколько более распространённый вариант: возврат на предыдущий экран после ввода данных – для этого служит функция esc. Такой порядок работы характерен для быстрого ввода большого количества первичных карточек, когда нет времени разглядывать экран read only, а экономия в один переклик на документ вполне ощутима. В упомянутых случаях вам придётся самостоятельно разработать do_update_$_REQUEST{type}.

Несколько чаще приходится писать специфическую do_update_$_REQUEST{type}, когда при записи скалярных данных требуется совершить какие-либо дополнительные действия. В частности, такое происходит, если форма ввода содержит поля, значения которых хранятся в другой таблице. Типичный пример – поле типа checkboxes, визуализирующее отношение многие-ко-многим. Побочные эффекты могут и вообще не затрагивать БД: например, рассылка e-mail.

download

Здесь мы наблюдаем некоторую методологическую непоследовательность: ранее было сказано, что понятие действия введено для того, чтобы выделить процедуры, изменяющие содержимое БД, однако приходится признать, что процедура do_download_$_REQUEST{type} должна только выдавать клиенту файл, ранее загруженный на сервер («Загрузка файлов»).

В ранних Eludia-приложениях для этого создавались отдельные типы экранов, для которых прописывалась только процедура get_item_of_$_REQUEST{type}. Но практика показала, что гораздо удобнее не замусоривать множество типов, а оформлять выдачу файлов и, к слову сказать, генерацию документов под распечатку, то есть практически всех не-HTML ответов в виде [псевдо]действий. Кстати, как и для настоящих действий, в данном случае всегда следует указывать в качестве целевого окна (target) вездесущий невидимый фрейм invisible.

Возвращаясь к теме выдачи файлов, отметим, что, как правило, она осуществляется при помощи процедуры sql_download_file, полностью симметричной ранее упоминавшейся sql_upload_file. Именно такое поведение заложено в do_download_DEFAULT.

Ещё полезно знать, что ссылка {action => 'download'} проставляется по умолчанию для всех полей ввода типа file. Таким образом, если для вашего документа достаточно одного файла и поля таблицы вы назвали стандартно, то программировать загрузку ни в ту (up), ни в другую (down) сторону не требуется.

delete

Тут объяснять особо нечего: речь идёт об удалении записи. Процедура do_delete_DEFAULT реализует безопасный вариант этой операции (мягкое удаление) и переопределять её стоит только в том случае, если требуется каскадное удаление дочерних записей или иные дополнительные опреации с данными.

В отличие от всех остальных действий, после delete адрес перехода не вычисляется из %_REQUEST; клиент всегда перенаправляется на предыдущий экран (esc).

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

undelete

Опять-таки всё очевидно: это действие обратно delete и обычно сводится к обнулению поля fake.

Правда, запись может оказаться скрытой не только в результате удаления, но и при слиянии. Поскольку требуется восстановить запись, скорее всего, слияние было ошибочным (это не те "Журавли"!). А в таком случае неплохо бы перенести на неё обратно все неправедно изменённые ссылки с других записей.

Данная функциональность реализуется процедурой sql_undo_relink, вызов которой заложен в do_undelete_DEFAULT. При этом используется информация о перемещённых ссылках, записываемая sql_do_relink в специальную таблицу. Естественно, автоматическое восстановление возможно только если Слияние и перенос ссылок были выполнены стандартным образом.

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

Можно, конечно, писать проверки в validate_undelete_$_REQUEST {type}, но обидно то, что они практически обязательно окажутся частным случаем проверок для действия update. Так что имеет смысл переопределить кнопку "Восстановить", чтобы она вела на экран редактирования (&__edit=1). Восстановление ссылок при этом производится по ходу sql_do_update. Глобально замена кнопки устанавливается опцией $conf -> {core_undelete_to_edit} или, более гибко, переопределением процедуры del.

add

Подобно тому, как update – стандартное действие для форм, add – стандартное действие для таблиц. Правда, если формы ввода без действия практически не имеют смысла, то формы, связанные с таблицами, используются относительно нечасто. В этом случае строки таблицы должны содержать поля ввода. Как правило, эти поля имеют тип checkbox, а активация формы должна приводить к добавлению выбранных строк к наперёд заданной записи в виде дочерних объектов (например, запись отмеченных сотрудников в мероприятие в качестве участников), откуда и название действия.

Процедура do_add_DEFAULT выполняет несколько иное действие: она осуществляет слияние дубликатов. Для того, чтобы это работало, форма редактирования записи должна содержать таблицу дубликатов, checkbox'ы на строках которой имеют имена вида _clone_$id. Если вы используете StEludio, то при создании get_item_of_$_REQUEST{type} и draw_item_of_$_REQUEST{type} вам будут предложены шаблоны кода, содержащие всё необходимое. Остаётся только подправить условие вычисления дубликатов для данной таблицы.

kill

Действие kill относится к delete так же, как add — к create: это массовый вариант операции удаления. Процедура do_kill_DEFAULT сканирует %_REQUEST на предмет непустых значений с ключами вида /_$_REQUEST{type}_(\d+)/ и осуществляет мягкое удаление для соответствующих записей. Шаблон кода процедуры draw_$_REQUEST{type}, заложенный в StEludio, генерирует описание нужного checkbox'а, а также кнопки для действий kill или unkill (см. ниже) в зависимости от текущего фильтра по полю fake.

unkill

Это прямое обращение операции kill: восстановление множества записей. Как и в случае с undelete, процедура do_unkill_DEFAULT производит возврат перемещённых ссылок на восстанавливаемые записи.

Протоколирование действий

Вряд ли стоит долго убеждать разработчика в том, что все действия в информационной системе должны протоколироваться. Особенно если ему приходилось мотивированно отвечать на строгий вопрос типа: "И куда же, по-твоему, могла деться эта платёжка?" Протоколы Apache и Mysql в принципе содержат ответы на такие вопросы, но быстро добыть оттуда нужные сведения крайне сложно ввиду их сильной зашумлённости. В общем, необходим протокол действий операторов, использующий ту же систему координат, что и приложение.

В Eludia такой протокол ведётся всегда, причём автоматически. Программист не может позабыть записать данные о том или ином действии, поскольку этим занимается подрпрограмма-обработчик. По результатам исполнения каждого редактирующего запроса она записывает в таблицу log:

dt
дату/время;
action
имя действия;
id_user
номер пользователя, инициировавшего запрос;
params
параметры HTTP-запроса;
error
сообщение об ошибке (если ошибки не произошло, то пустое значение).

Номер последней протокольной записи доступен в процедурах do_$_REQUEST{action}_$_REQUEST{type} как $_REQUEST {_id_log}. И do_update_DEFAULT записывает его в таблицу автоматически, если только описание схемы данных содержит упоминание поля id_log.

Сетевые координаты пользователя

Помимо указанных, таблица log содержит также поля ip и ip_fw, в которые записываются, соответственно, $ENV {REMOTE_ADDR} и $ENV {HTTP_X_FORWARDED_FOR}.

Кроме того, если параметр $preconf -> {core_no_log_mac} определён и имеет ложное значение (например, 0), то в таблице log дополнительно создаётся поле mac, куда, при определённом везении, записываются MAC-адреса клиентов. Вычисляются они, исходя из того же $ENV {REMOTE_ADDR} + команды arp. То, что таким образом удастся вычислить реальный MAC, далеко не гарантировано, а некоторое время на ожидание будет потрачено обязательно. Поэтому сейчас по умолчанию запись MAC-адресов не ведётся, хотя когда-то она предполагалась обязательной. Эти исторические обстоятельства объясняют странный способ задания параметра core_no_log_mac.

Данные о запросе

На протяжении 6 лет использования Eludia.pm во всех приложениях использовался фиксированный формат таблицы log. С течением времени стали проявляться его недостатки, особенно актуальные для определённых СУБД. Поэтому в последних версиях логгирование вынесено в подключаемый модуль, который можно либо выбирать из 2 стандартных, либо разрабатывать самостоятельно. Модуль должен быть виден относительно @INC как "Eludia/Content/Log/${some_name}.pm". Имя используемого модуля определяется как $conf -> {core_log} -> {version}, по умолчанию оно принимается равным 'v1'.

v1

Это традиционный формат лога приложения, использовавшийся с первых прототипов Eludia.pm. Для него определяются поля:

object_type
тип;
object_id
номер объекта.

а содержимое params формируется из %_REQUEST_VERBATIM при помощи Data::Dumper с заменой стандартного имени переменной $VAR1 на $_REQUEST:

$_REQUEST = {type => 'users', action => 'add'}

v2

Во второй версии стандартного логгера пара полей object_type / object_id заменена на:

href
если определён номер объекта, то строка вида "$_REQUEST{type}&id=$_REQUEST{id}", иначе — просто $_REQUEST{type};

Таким образом, приписав '/?type=' спереди к данному значению, можно получить ссылку на соответствующий экран приложения.

Значения для поля params в данной версии имеют формат JSON, однако 1-й и последний символ в таблицу не записываются (они всегда равны '{' и '}' соответственно). Сериализируется копия хэша %_REQUEST_VERBATIM, из которой удалены все компоненты, перечисленные в списке $preconf -> {core_log} -> {suppress} -> {always}, а также компоненты с пустыми значениями, перечисленные в списке $preconf -> {core_log} -> {suppress} -> {empty}. Если эти списки не определены в настройке экземпляра приложения, то они заполняются в процедуре check_module_log, определённой в файле Eludia.pm. Состав этих списков по умолчанию подобран так, чтобы "забывание" соответствующих компонент было не существенно для последующего анализа log'а.

"type":"users","action":"add"

В v2 поле params имеет тип TEXT (в отличие от LONGTEXT для v1), который, в частности для Oracle транслируется с VARCHAR2. Это сделано для того, чтобы избавиться от избыточной вычислительной стоимости типа CLOB, особенно досадной в силу того, что значение params практически всегда укладывается в VARCHAR.

Но что произойдёт, если params всё-таки окажутся слишком длинными? Такое значение будет "разрезано" и записано в несколько разных строк таблицы log. В конце каждого отрезка, кроме последнего, будет записан символ '…' и id записи-продолжения:

"type":"users","act…12878

В самих записях-продолжениях пусты все поля, кроме id, dt и params. В частности, признаком того, что данная запись является продолжением, можно считать пустоту поля action. А условие того, что данная строка требует продолжения — это RegExp /…(\d+)$/ (в JSON внутренность хэша может оканчиваться на незакавыченное натуральное число, но тогда ему предшествует только ':', а никак не '…').

Персональные инструменты
Пространства имён

Варианты
Действия
Навигация
Разработчику
Администратору
Инструменты