Текущие изменения в коде и архитектуре движка
Этот пост больше предназначен для тех, кто использует ЛС на уровне кода. Постараюсь ввести в курс того, что происходит сейчас в SVN проекта над кодом и архитектурой.
Сейчас наметились три основные изменения:
Теперь более подробно о каждом.
В коде это выглядит так:
получение автора блога из сущности топика
Вот такие дела сейчас происходят :) Принимаю вопросы и предложения по сабжу и вообще по архитектуре.
UPD также меня постоянно посещают мысли избавиться от констант в конфиге и вынести настройки в массив $CONFIG либо БД :)
Сейчас наметились три основные изменения:
- изменения логики работы с БД и кешем
- создание универсальных комментариев
- возможность обрабатывать Ajax запросы прямо в экшенах
Теперь более подробно о каждом.
Изменения логики работы с БД и кешем.
Сюда входит избавление в запросах к БД от JOIN'ов, теперь при запросе тянутся только ID необходимых сущностей, а дальше в модулях они(сущности) «обрастают» необходимыми данными. В результате мы получаем один запрос на выборку списка основных данных и по одному запросу на каждый набор дополнительных данных. Дополнительные наборы данных получаются путем мульти-запросов к кешу и БД. Преимущества такого подхода в том, что данные будут более активно кешироваться, а запросы к БД быстрее исполняться. Например, теперь при голосовании за топик кеш страницы со списком топиков сбрасываться не будет, сбросится только кеш конкретного топика.В коде это выглядит так:
получение автора блога из сущности топика
$oTopic->getBlog()->getOwner()
получение дополнительных данных
public function GetTopicsAdditionalData($aTopicId,$aAllowData=array('user','blog','vote','favourite','comment_new')) {
func_array_simpleflip($aAllowData);
if (!is_array($aTopicId)) {
$aTopicId=array($aTopicId);
}
/**
* Получаем "голые" топики
*/
$aTopics=$this->GetTopicsByArrayId($aTopicId);
/**
* Формируем ID дополнительных данных, которые нужно получить
*/
$aUserId=array();
$aBlogId=array();
foreach ($aTopics as $oTopic) {
if (isset($aAllowData['user'])) {
$aUserId[]=$oTopic->getUserId();
}
if (isset($aAllowData['blog'])) {
$aBlogId[]=$oTopic->getBlogId();
}
}
/**
* Получаем дополнительные данные
*/
$aTopicsVote=array();
$aFavouriteTopics=array();
$aTopicsQuestionVote=array();
$aTopicsRead=array();
$aUsers=isset($aAllowData['user']) && is_array($aAllowData['user']) ? $this->User_GetUsersAdditionalData($aUserId,$aAllowData['user']) : $this->User_GetUsersAdditionalData($aUserId);
$aBlogs=isset($aAllowData['blog']) && is_array($aAllowData['blog']) ? $this->Blog_GetBlogsAdditionalData($aBlogId,$aAllowData['blog']) : $this->Blog_GetBlogsAdditionalData($aBlogId);
if (isset($aAllowData['vote']) and $this->oUserCurrent) {
$aTopicsVote=$this->GetTopicsVoteByArray($aTopicId,$this->oUserCurrent->getId());
$aTopicsQuestionVote=$this->GetTopicsQuestionVoteByArray($aTopicId,$this->oUserCurrent->getId());
}
if (isset($aAllowData['favourite']) and $this->oUserCurrent) {
$aFavouriteTopics=$this->GetFavouriteTopicsByArray($aTopicId,$this->oUserCurrent->getId());
}
if (isset($aAllowData['comment_new']) and $this->oUserCurrent) {
$aTopicsRead=$this->GetTopicsReadByArray($aTopicId,$this->oUserCurrent->getId());
}
/**
* Добавляем данные к результату - списку топиков
*/
foreach ($aTopics as $oTopic) {
if (isset($aUsers[$oTopic->getUserId()])) {
$oTopic->setUser($aUsers[$oTopic->getUserId()]);
} else {
$oTopic->setUser(null); // или $oTopic->setUser(new UserEntity_User());
}
if (isset($aBlogs[$oTopic->getBlogId()])) {
$oTopic->setBlog($aBlogs[$oTopic->getBlogId()]);
} else {
$oTopic->setBlog(null); // или $oTopic->setBlog(new BlogEntity_Blog());
}
if (isset($aTopicsVote[$oTopic->getId()])) {
$oTopic->setUserIsVote(true);
$oTopic->setUserVoteDelta($aTopicsVote[$oTopic->getId()]->getDelta());
} else {
$oTopic->setUserIsVote(false);
}
if (isset($aFavouriteTopics[$oTopic->getId()])) {
$oTopic->setIsFavourite(true);
} else {
$oTopic->setIsFavourite(false);
}
if (isset($aTopicsQuestionVote[$oTopic->getId()])) {
$oTopic->setUserQuestionIsVote(true);
} else {
$oTopic->setUserQuestionIsVote(false);
}
if (isset($aTopicsRead[$oTopic->getId()])) {
$oTopic->setCountCommentNew($oTopic->getCountComment()-$aTopicsRead[$oTopic->getId()]->getCommentCountLast());
$oTopic->setDateRead($aTopicsRead[$oTopic->getId()]->getDateRead());
} else {
$oTopic->setCountCommentNew(0);
$oTopic->setDateRead(date("Y-m-d H:i:s"));
}
}
return $aTopics;
}
public function GetTopicsByArrayId($aTopicId) {
if (!is_array($aTopicId)) {
$aTopicId=array($aTopicId);
}
$aTopicId=array_unique($aTopicId);
$aTopics=array();
$aTopicIdNotNeedQuery=array();
/**
* Делаем мульти-запрос к кешу
*/
$aCacheKeys=func_build_cache_keys($aTopicId,'topic_');
if (false !== ($data = $this->Cache_Get($aCacheKeys))) {
/**
* проверяем что досталось из кеша
*/
foreach ($aCacheKeys as $sValue => $sKey ) {
if (array_key_exists($sKey,$data)) {
if ($data[$sKey]) {
$aTopics[$data[$sKey]->getId()]=$data[$sKey];
} else {
$aTopicIdNotNeedQuery[]=$sValue;
}
}
}
}
/**
* Смотрим каких топиков не было в кеше и делаем запрос в БД
*/
$aTopicIdNeedQuery=array_diff($aTopicId,array_keys($aTopics));
$aTopicIdNeedQuery=array_diff($aTopicIdNeedQuery,$aTopicIdNotNeedQuery);
$aTopicIdNeedStore=$aTopicIdNeedQuery;
if ($data = $this->oMapperTopic->GetTopicsByArrayId($aTopicIdNeedQuery)) {
foreach ($data as $oTopic) {
/**
* Добавляем к результату и сохраняем в кеш
*/
$aTopics[$oTopic->getId()]=$oTopic;
$this->Cache_Set($oTopic, "topic_{$oTopic->getId()}", array("topic_update_{$oTopic->getId()}"), 60*60*24*4);
$aTopicIdNeedStore=array_diff($aTopicIdNeedStore,array($oTopic->getId()));
}
}
/**
* Сохраняем в кеш запросы не вернувшие результата
*/
foreach ($aTopicIdNeedStore as $sId) {
$this->Cache_Set(null, "topic_{$sId}", array("topic_update_{$sId}"), 60*60*24*4);
}
/**
* Сортируем результат согласно входящему массиву
*/
$aTopics=func_array_sort_by_keys($aTopics,$aTopicId);
return $aTopics;
}
Думаю всё должно быть понятно :) Но есть один нюанс, библиотека dkCache не поддерживает мульти-запросы к кешу через memcache. Поэтому сейчас работаю псевдо мульти-запросы, в дальнейшем планирую доработать её, т.к. в этом огромный плюс и memcache этим выгодно отличается от файлового кеширования. Так что обладателям файлового кеша придеться смириться с возросшим в разы числом запросов к кешу. Создание универсальных комментариев.
Было принято решение сделать один общий функционал комментариев и хранить их в одной таблице. Это позволит без особых затрат прикручивать комменты к любым объектам. Такой же подход планируется сделать для голосований и избранного.Возможность обрабатывать Ajax запросы прямо в экшенах.
В экшены добавлен функционал по обработке Ajax запросов и отдачи выходных данных в формате jsHttpRequest и JSON. Это позволит более удобно писать ajax обработчики и в ряде случаев не дублировать код. Пример обработки ajax запроса в экшене можно посмотреть на примере добавление комментариев в ActionBlog.class.php. Причем добавление комментария может происходит и с отключенным JavaScript у клиента. Во Viiew появились для этого новые методы:
/**
* Устанавливает тип отдачи при ajax запросе, если null то выполняется обычный вывод шаблона в браузер
*
* @param unknown_type $sResponseAjax
*/
public function SetResponseAjax($sResponseAjax='jsHttpRequest') {
/**
* Проверка на безопасную обработку ajax запроса
*/
if ($sResponseAjax) {
$this->Security_ValidateSendForm();
}
$this->sResponseAjax=$sResponseAjax;
}
/**
* Загружаем переменную в ajax ответ
*
* @param unknown_type $sName
* @param unknown_type $value
*/
public function AssingAjax($sName,$value) {
$this->aVarsAjax[$sName]=$value;
}
А использовать это так:
/**
* Обработка добавление комментария к топику через ajax
*
*/
protected function AjaxAddComment() {
$this->Viewer_SetResponseAjax();
$this->SubmitComment(); // здесь происходит обычная обработка добавления комментария
}
Вот такие дела сейчас происходят :) Принимаю вопросы и предложения по сабжу и вообще по архитектуре.
UPD также меня постоянно посещают мысли избавиться от констант в конфиге и вынести настройки в массив $CONFIG либо БД :)
55 комментариев
А по поводу архитектуры — вроде пока всё утраивает. от только вопрос. Почему не все конфиги модулей обрабатываются? Конкретно константы.
зы. и правильный ход в сторону упрощения и систематизации AJAX. В теории можно в будущем вообще всё подгружать частично кусками шаблонов.
зы. Но вы меня не переубедили.
Да, мне тоже интересно, какова генеральная линия развития проекта? Значит ли все вышескзанное, что есть четкая нацеленность на то, что это заведомо будет «тяжелое» приложение, расчитанное на приличный хостинг с мемкешем, сфинксом и проч. серьезными атрибутами?
ning и vsevteme.ru
А если нужно кастомайзное решение на приличное кол-во народа, то livestreet идеален.
Правда, нужно составлять мануалы ко всему.
Хотя и можно свой домен привязать
ДАВНО пора! Но, только не в БД.
Вообще, в идеале, лучше сделать несколько файлов настроек: глобальные настройки и возможность в модуле иметь свой файл настроек.
И еще — может, не стоит всегда подгружать все-все конфиг-скрипты для всех модулей? Общие конфиги грузить всегда, а те, что к конкрентным модулям относятся, можно подгружать при инициализации соотвествующих классов. Когда модулей не много, это не суть важно. Но мы же все считаем, что проект будет развиваться и расти и модулей со временм будет дофига :)
осталось там же конфиги подгружать по названию модуля, а в LoadConfig() грузить тока autoLoad
Макс, ты тут? чего думаешь?:)
Я как-то первый раз прочитав про config.local.php не отразил, что от не зависит от модулей. :-)
Большая часть модулей может быть в виде топиков (топик-видео, топик-холивар, топик-фотоколлаж итп.
Соответственно, чтобы можно было добавлять свои виды топика без изменений, нам нужно:
1. создать сеттер и геттер объекта (например, хуком привязываем фотоколлаж к топику:
2. Потом в списке топиков (в шаблоне) мы можем забирать каждый объект:
только получение объекта сущности будет происходит через обертку в $oEngine, например $oEngine::GetEntity('TopicBlabla',aParam);
По этому нужен будет метод, который возвращает название класса объекта. Кстати, уже есть TopicType, я про него забыл. Тока его надо расширить в бд, чего-нибудь сделав с ENUM…
А данные в новые классы грузить надо через хуки… я не совсем понимаю, как у тебя это будет происходит.
1. Вот в topic.class.php у нас появился массив id.
2. Мы подтянули к нему данные для объекта топик в цикле
3. Мы берем эту коллекцию топиков в экшене или блоке и выводим в смарти.
На каком этапе у нас будет создаваться дочерний класс через хук? На этапе 2, в цикле?
Не мог бы ты config.php приспособить для запуска через шел?
Во-первых там нет массива $_SERVER
Так что можно вставить
Либо убрать вообще DIR_WEB_ROOT при запуске из консоли
А дальше вместо
Ставить
с учетом того, что $sDirRoot мы устанавливаем в запускаемом файле…
Либо нужно делить конфигурацию путей и другие константы на разные конфиги…
Надо его не грузить чтоли…