Модернизация модуля Message - жизнь после редиректа
Описание проблемы.
Создавая новый модуль или экшн, очень часто прибегаю к использованию сообщений Message_AddNotice() и Message_AddError(). Штука очень симпатичная и удобная. Но. Всегда есть одно но. И заключается оно в том, что модуль выводит сообщения только на «текущей» странице. Если же где-то в экшене выполняется редирект, то наше сообщение теряется.
Почему это неудобно? Давайте посмотрим конкретный пример. Я разрабатываю модуль «Гараж». По адресу /garage/user_login можно увидеть список машин пользователя, если просматривающий пользователь = текущему авторизированному, то у каждого автомобиля есть ссылка «Удалить». Эта ссылка указывает на /garage/user_login/del/car_id, где происходит обработка удаления. За это отвечает EventDeleteCar() и именно здесь удобно сказать пользователю «Парень, твоя машина успешно удалена», ну или «Сорри, не получается». Для этого используем стандартное Message_AddNotice(), Message_AddError().
Но, мы не можем оставить пользователя на этой страничке, и в конце реализации event`а перекидываем его назад к списку с помощью func_header_location(). И все. Никакого сообщения пользователь не увидит!
Идея решения.
В Zend Framework есть action-плагин FlashMessenger, который для тех же целей использует сессию. Мы поступим аналогично.
Собственно, решение.
1. Открываем файл /classes/modules/sys_message/Message.class.php
2. Добавляем новую переменную и два новых метода.
3. Теперь немного подкорректируем метод Shutdown(). Он будет выглядеть так:
4. Теперь открываем наш Action (у меня это ActionGarage), и в коде EventDeleteCar() меняем
на
А перед редиректом вызываем Shutdown() модуля Message.
5. Еще один немаловажный шаг! Для того, чтобы сообщение было выведено на следующей странице, в модуле Message должен сработать Shutdown(). А для этого, модуль должен быть загружен в Engine(). К сожалению, метод LoadModule() класса Engine() protected, воспользоваться им не получиться. Единственный выход, который я нашел — «фиктивный» вызов любого геттера (фиктивный, потому что нам не нужен результат работы геттера, нам просто нужно чтобы модуль инициализировался и попал в очередь обработки Engine).
Поэтому в EventShutdown() нашего Action`а добавляем следующий код:
6. Радуемся своему хоть и маленькому, но достижению!
Хоть после удаления автомобиля выполняется редирект, пользователь все равно увидит сообщение:
Аналогичный функционал очевидно понадобиться и на странице «добавить» — если произошла ошибка, то мы сообщаем пользователю тут же, а если все прошло успешно, то есть смысл «отправить» пользователя к списку своих машин и кинуть «отложенное» сообщение.
Финальный аккорд — реализация аналогичных функций для сообщений об ошибках, но это уже дело техники.
Создавая новый модуль или экшн, очень часто прибегаю к использованию сообщений Message_AddNotice() и Message_AddError(). Штука очень симпатичная и удобная. Но. Всегда есть одно но. И заключается оно в том, что модуль выводит сообщения только на «текущей» странице. Если же где-то в экшене выполняется редирект, то наше сообщение теряется.
Почему это неудобно? Давайте посмотрим конкретный пример. Я разрабатываю модуль «Гараж». По адресу /garage/user_login можно увидеть список машин пользователя, если просматривающий пользователь = текущему авторизированному, то у каждого автомобиля есть ссылка «Удалить». Эта ссылка указывает на /garage/user_login/del/car_id, где происходит обработка удаления. За это отвечает EventDeleteCar() и именно здесь удобно сказать пользователю «Парень, твоя машина успешно удалена», ну или «Сорри, не получается». Для этого используем стандартное Message_AddNotice(), Message_AddError().
Но, мы не можем оставить пользователя на этой страничке, и в конце реализации event`а перекидываем его назад к списку с помощью func_header_location(). И все. Никакого сообщения пользователь не увидит!
Идея решения.
В Zend Framework есть action-плагин FlashMessenger, который для тех же целей использует сессию. Мы поступим аналогично.
Собственно, решение.
1. Открываем файл /classes/modules/sys_message/Message.class.php
2. Добавляем новую переменную и два новых метода.
/**
* Массив сообщений, который будут показаны на СЛЕДУЮЩЕЙ страничке
* @var array
*/
protected $aMsgNoticeSession=array();
/**
* Добавляет новое сообщение, СЛЕДУЮЩЕЙ страничке
*
* @param string $sMsg
* @param string $sTitle
*/
public function AddNoticeSession($sMsg,$sTitle=null) {
$this->aMsgNoticeSession[]=array('msg'=>$sMsg,'title'=>$sTitle);
}
/**
* Возвращает список сообщений,
* которые необходимо поместить в сессию
*
* @return array
*/
public function GetNoticeSession() {
return $this->aMsgNoticeSession;
}
3. Теперь немного подкорректируем метод Shutdown(). Он будет выглядеть так:
/**
* При завершении работы модуля передаем списки сообщений в шаблоны Smarty
*
*/
public function Shutdown() {
$this->Viewer_Assign('aMsgError',$this->GetError());
// Строки ниже нужно добавить
// Логика здесь такая - получаем сообщения, которые содержаться в сессии
// и добавляем их к выводимым. А те сообщения, которые были добавлены
// текущими экшенами (в этом сеансе), вкладываем в сессию.
$sNoticeSession = $this->Session_Get('message_notice_session');
$aNotice=(!$sNoticeSession)
? $this->GetNotice()
: array_merge($this->GetNotice(), (array)unserialize($sNoticeSession));
$this->Session_Set('message_notice_session', serialize($this->GetNoticeSession()));
$this->Viewer_Assign('aMsgNotice',$aNotice);
}
4. Теперь открываем наш Action (у меня это ActionGarage), и в коде EventDeleteCar() меняем
$this->Message_AddNotice('Машина успешно удалена.');
на
$this->Message_AddNoticeSession('Машина успешно удалена.');
А перед редиректом вызываем Shutdown() модуля Message.
$this->Message_Shutdown();
func_header_location(DIR_WEB_ROOT.'/garage/'. $oUserCurrent->getLogin());
5. Еще один немаловажный шаг! Для того, чтобы сообщение было выведено на следующей странице, в модуле Message должен сработать Shutdown(). А для этого, модуль должен быть загружен в Engine(). К сожалению, метод LoadModule() класса Engine() protected, воспользоваться им не получиться. Единственный выход, который я нашел — «фиктивный» вызов любого геттера (фиктивный, потому что нам не нужен результат работы геттера, нам просто нужно чтобы модуль инициализировался и попал в очередь обработки Engine).
Поэтому в EventShutdown() нашего Action`а добавляем следующий код:
$this->Message_GetNotice();
6. Радуемся своему хоть и маленькому, но достижению!
Хоть после удаления автомобиля выполняется редирект, пользователь все равно увидит сообщение:
Аналогичный функционал очевидно понадобиться и на странице «добавить» — если произошла ошибка, то мы сообщаем пользователю тут же, а если все прошло успешно, то есть смысл «отправить» пользователя к списку своих машин и кинуть «отложенное» сообщение.
Финальный аккорд — реализация аналогичных функций для сообщений об ошибках, но это уже дело техники.
6 комментариев
на счет Shutdown, загрузку сообщений из сессии лучше делать при инициализации модуля, а фиктивный метод get заменить на автозагрузку модуля в конфиге config.module.php
Да, об этом не подумал. Архитектурно, так, конечно, будет правильнее.
А отдельный метод я сделал потому, что функционал кардинально разный. Там выводим на этой странице, а там — на следующей. Хотя тут дело стиля.
Да, Макс, это нужно обязательно в системный функционал воткнуть.
Почему вы избрали такой путь формирования объектов $this->Session_Get('message_notice_session');,
а не такой $this->Session->Get('message_notice_session');
Ведь с точки зрения OOP он наиболее верен