Набросал вот в пятницу класс для работы с различными видами каталогов и документов. Подобный класс успешно используется, например, на сайте _corbina.tv, этот немного оптимизирован и доработан. Хотелось бы услышать конструктивную критику и предложения по улучшению функционала. Использование: Создание объекта. PHP: // в конструкторе необходимо указать тип работы с иерархией [b]$catalog = new Catalog(Catalog::FULLUPDATETREE );[/b] PHP: const C2_FULLUPDATETREE = 1; // обновлять кол-во документов в категориях от текущей категории до корня const C2_ONLYPARENTUPDATETREE = 0; // обновлять кол-во документов только в текущей категории отключение обновления кол-ва документов не делал, т.к. еще в практике использования каталогизаторов такого не было необходимости. Категории В качестве категорий я взял жесткую структуру таблицы категорий - заголовок, короткая аннотация, большой текст - описание, изображение. (!)Изображения я храню в отдельной таблице, потому в классе использую в качестве ссылки на изображение его id в другой таблице. В таблице категорий так же присутствует поле section, оно может быть либо числом, либо текстом. лично я юзаю текстовые названия секций. Если на сайте необходимо использовать множество разных каталогов, то секции тут в помощь. по умолчанию у секции значение default PHP: /** * Добавление категории/нода * * @param int $parentid ID родителя. У root id = 1 * @param string $title Заголовок категории * @param string $note Краткое описание категории * @param string $text Полное описание категории * @param int $imageid ID изображения * * @return bool|int Результат добавления false или ID вставленной категории */ [b]$category->addNode(1, 'Название категории', 'краткое описание','длинное описание', false);[/b] /** * Редактирование категории/нода * * @param int|array $id ID категории/нода * @param string $title Заголовок категории * @param string $note Краткое описание категории * @param string $text Полное описание категории * @param int $imageid ID изображения * * @return bool|int Результат добавления false или ID вставленной категории */ [b]$catalog->updateNode(2, 'Новое название категории', '', '', false);[/b] /** * Перемещение категории/нода * * @param int|array $id ID категории/нода * @param int $new_parent_id ID нового родителя категории/нода * * @return bool Результат перемещения */ [b]$catalog->moveNode(3, 2);[/b] // прошу обратить внимание, что для перемещения нодов, можно передавать в качестве ID целое число или массив целых чисел в качестве ID категорий. Т.е. категории можно перемещать пачкой /** * Удаление категории/нода * * @param int $id ID категории/нода * @param bool $fullDelete true - удалять физически / false - использовать флажок удаления в БД * @return bool true - удаление успешно / false - какая-то ошибка */ [b]$catalog->deleteNode(3, false);[/b] // при удалении категории, удаляются и все подкатегории. (!)Документы при этом не затрагиваются, их необходимо удалять отдельно. Можно удалять физически, либо используя флаг удаления (рекомендуется, для избежания "не то удалил и теперь не восстановить", так же для избежания фрагментации) Короткие и длинные описания мне обычно необходимы были в интернет магазинах Работа с документами Для работы с документами, у нас бОльший простор для фантазии со структурой таблиц. единственное условие - в таблице документов обязательно должно быть поле category_id. если вам необходимо, чтобы документ мог находиться одновременно в нескольких категориях, то category_id должен быть (var)char. Иначе достаточно smallint, или даже tinyint. PHP: /** * Добавление документа в БД * * @param int|array $category_id * @param array $data * @return bool */ [b]$category->addDocument($category_id, $data);[/b] /** * Удаление документа из БД * * @param int|array $id * @param bool $fullDelete * @return bool */ [b]$category->deleteDocument($id, $fullDelete = false);[/b] /** * Редактирование документа в БД * Для перемещения документа в другую категорию достаточно просто указать новые категории в $data * * @param int $id ID документа * @param array $data Данные документа * @return bool */ [b]$category->updateDocument($id, $data);[/b] /** * Перемещаем документ(ы) в новую категорию(и) * * @param int|array $id * @param int|array $new_category_id * @param bool $updateDocument Необходимо ли обновлять поле category_id у текущего документа(ов), по умолчаанию true * @return bool */ [b]$category->moveDocument($id, $new_category_id, $updateDocument = true);[/b] /** * Вернуть документ по его id * * @param int $id ID документа * @param bool $updateViews Необходимо ли обновлять счетчик просмотров. По умолчанию true * @return array Массив с докумеентом */ [b]$category->getDocumentById($id, $updateViews = true);[/b] В классе заведомо упущены 2 метода. Если вы знакомы с php вам не составит труда выделить 2 минуты на их написссание. Вот сам класс: PHP: <?php /** * Класс для управления каталогами и документами * Каталоги фиксированные для всех. Документы абстрактны. */ class Catalog{ // Константы const C2_FULLUPDATETREE = 1; const C2_ONLYPARENTUPDATETREE = 0; // Внутренние переменные private $_tblCategories = 'in_catalog_new'; // название таблицы каталога private $_tblDocuments = 'in_catalog_documents'; // название таблицы документов private $_section = 'default'; // ID секции private $_cacher; private $_cacheKeyCategory = ''; // ключ для кеширования нодов. задаются в конструкторе private $_cacheKeyDocuments = ''; // ключ для кеширования доков. задаются в конструкторе private $_categoryUpdateMethod = 0; private $_nodes = array(); // массив нод/категорий private $_paths = array(); // массив путей до корня public function __construct($updateMethod = self::C2_FULLUPDATETREE){ $this->_cacher = Config::get('cacher'); $this->_cacheKeyCategory = 'catalog-categories-'.$this->_section; $this->_cacheKeyDocuments = 'catalog-documents-' .$this->_section; $this->_categoryUpdateMethod = $updateMethod; if($cache = $this->_cacher->read($this->_cacheKeyCategory)){ $this->_nodes = $cache['_nodes']; $this->_paths = $cache['_paths']; }else{ // загружаем массив категорий $this->_nodes = $this->_getNodeList(); // создаем путь к корню для каждой категории $this->_paths = $this->_compilePathToRoot($this->_nodes, 1); // сохраняем в кеше навечно. обновляем только при обновлении категорий $this->_cacher->write($this->_cacheKeyCategory, array('_nodes'=>$this->_nodes, '_paths'=>$this->_paths), null, 0); } } /**** УПРАВЛЕНИЕ КАТАЛОГАМИ ****/ /** * Добавление категории/нода * * @param int $parentid ID родителя * @param string $title Заголовок категории * @param string $note Краткое описание категории * @param string $text Полное описание категории * @param int $imageid ID изображения * * @return bool|int Результат добавления false или ID вставленной категории */ public function addNode($parent_id = 1, $title, $note = '', $text = '', $imageid = null){ if(!$title) return; $this->_clearCache(true); // чистим кеш return DB::insert( $this->_tblCategories, array( 'parent_id' => $parent_id, 'section' => $this->_section, 'posttime' => time(), 'user_id' => Auth::$id, 'title' => $title, 'note' => $note, 'text' => $text, 'image_id' => $imageid ) ); } /** * Редактирование категории/нода * * @param int|array $id ID категории/нода * @param string $title Заголовок категории * @param string $note Краткое описание категории * @param string $text Полное описание категории * @param int $imageid ID изображения * * @return bool|int Результат добавления false или ID вставленной категории */ public function updateNode($id, $title = '', $note = '', $text = '', $imageid = null){ if(!$id || !$title) return; $this->_clearCache(true); // чистим кеш $data = array( 'title' => $title, 'note' => $note, 'text' => $text, ); if($imageid) $data['image_id'] = $imageid; return DB::update( $this->_tblCategories, $data, is_array($id) ? '`id` IN ('.join(',',$id).')' : '`id`='.$id ); } /** * Редактирование категории/нода * * @param int|array $id ID категории/нода * @param int $new_parent_id ID нового родителя категории/нода * * @return bool Результат перемещения */ public function moveNode($id, $new_parent_id){ if(!$id || !$new_parent_id) return; $this->_clearCache(true); // чистим кеш return DB::update( $this->_tblCategories, array('parent_id' => $new_parent_id), is_array($id) ? '`id` IN ('.join(',',$id).')' : '`id`='.$id ); } /** * Удаление категории/нода * * @param int $id ID категории/нода * @param bool $fullDelete true - удалять физически / false - использовать флажок удаления * @return bool true - удаление успешно / false - какая-то ошибка */ public function deleteNode($id, $fullDelete = false){ if(!$id) return false; $this->_clearCache(true); // чистим кеш // получаем массив ID всех затронутых веток $ids = join(',', $this->_compileChilds($this->_nodes, $id)); //TODO: что делать с доками? if(!$fullDelete){ // проставляем флаги удаления DB::update( $this->_tblCategories, array('deleted' => true), '`id` IN ('.$ids.')' ); }else{ // удаляем из БД физически DB::delete( $this->_tblCategories, '`id` IN ('.$ids.')' ); } return true; } /**** УПРАВЛЕНИЕ ДОКУМЕНТАМИ ****/ /** * Добавление документа в БД * * @param int|array $category_id * @param array $data * @return bool */ public function addDocument($category_id, $data){ $data['category_id'] = is_array($category_id) ? join(',',$category_id) : $category_id; // увеличиваем кол-во документов в категории на 1 $this->_updateCounter($category_id, 1, $this->_categoryUpdateMethod); return DB::insert( $this->_tblDocuments, $data ); } /** * Удаление документа из БД * * @param int|array $category_id * @param array $data * @return bool */ public function deleteDocument($id, $fullDelete = false){ $d = $this->getDocument($id); $category_id = (array)explode(',', $d['category_id']); // уменьшаем кол-во документов в категории на 1 $this->_updateCounter($category_id, -1, $this->_categoryUpdateMethod); return DB::delete( $this->_tblDocuments, '`id` = '.$id ); } /** * Редактирование документа в БД * Для перемещения документа в другую категорию достаточно просто указать новые категории в $data * * @param int $id ID документа * @param array $data Данные документа * @return bool */ public function updateDocument($id, $data){ $doc = $this->getDocumentById($id, false); $data['category_id'] = is_array($data['category_id']) ? join(',',$data['category_id']) : $data['category_id']; if($data['category_id'] != $doc['category_id']) // если категория(и) изменились, $this->moveDocument($id, $data['category_id'], false); // то необходимо перенести документ return DB::update( $this->_tblDocuments, $data, '`id`=' . $id ); } /** * Перемещаем документ(ы) в новую категорию(и) * * @param int|array $id * @param int|array $new_category_id * @param bool $updateDocument Необходимо ли обновлять поле category_id у текущего документа(ов), по умолчааааанию true * @return bool */ public function moveDocument($id, $new_category_id, $updateDocument = true){ $ids = (array)$id; // приводим к общему виду foreach($ids as $id){ $doc = $this->getDocumentById($id); // уменьшаем кол-во документов в категории на 1 $this->_updateCounter($doc['category_id'], -1, $this->_categoryUpdateMethod); // увеличиваем кол-во документов на 1 в новых категориях $this->_updateCounter($new_category_id, 1, $this->_categoryUpdateMethod); if($updateDocument) DB::update($this->_tblDocuments, array('category_id'=>$new_category_id), '`id`=' . $id); } return true; } /** * Вернуть документ по его id * * @param int $id ID документа * @param bool $updateViews Необходимо ли обновлять счетчик просмотров. По умолчанию true * @return array Массив с докумеентом */ public function getDocumentById($id, $updateViews = true){ // обновляем число просмотров if($updateViews) DB::query('UPDATE `'.$this->_tblDocuments.'` SET `views`=`views`+1 WHERE `id`='.$id); return DB::selectRow(' SELECT * FROM `'.$this->_tblDocuments.'` WHERE `id`=' . (int)$id ); } /**** СЛУЖЕБНЫЕ ФУНКЦИИ ****/ /** * Обновление счетчика документов в ноде * * @param int|array $category_id * @param int $num * @param int $updateFullTree Если true, то обновляется весь путь до корня, иначе только текущая категория * @return bool */ private function _updateCounter($category_id, $num, $updateFullTree = false){ if(!is_array($category_id)) // приведем к общему виду $category_id[]=$category_id; if($updateFullTree){ // если надо обновить весь путь, то // пробегаемся по каждой категории, получаем весь путь до корня, и весь путь до корня обновляем foreach($category_id as $id) $ids = array_merge((array)$ids, $this->_paths[ $id ]); $category_id = join(',',array_merge((array)$ids, $category_id)); // получили список всех затронутых веток } return DB::query(' UPDATE `'.$this->_tblCategories.'` SET `count` = `count` + '.$num.' WHERE `id` IN ('.join(',',$category_id).')' ); } /** * Возвращает массив всех веток * * @return array */ private function _getNodeList(){ return DB::select(' SELECT * FROM `'.$this->_tblCategories.'` WHERE `section`=\''.$this->_section.'\' AND `deleted` = 0 '); } /** * Компиляция путей к корню для каждой категории * * @param int $id ID категории/верхушки для построения дерева */ private function _compilePathToRoot($nodes, $parent_id = 1, &$path = array()){ static $out = array(); foreach($nodes as $node){ if($node['parent_id'] == $parent_id){ $path[]=$node['parent_id']; $out[ $node['id'] ] = $path; unset($nodes[ $node['id'] ]); // чтоб по использованным уже нодам не бегать $this->_compilePathToRoot($nodes, $node['id'], $path); $path = array(); } // в основной массив добавляем URI пути к корню if(isset($out[ $node['id'] ])){ $_s = array(); foreach($out[ $node['id'] ] as $id=>$_t) $_s[] = $this->_nodes[$_t]['name']; $this->_nodes[ $node['id'] ]['path'] = '/'.join('/', $_s).'/'.$this->_nodes[$node['id']]['name']; }else $this->_nodes[ $node['id'] ]['path'] = '/'; } return $out; } /** * Компилируем все ветки от корня $id * * @param array $nodes * @param int $id * @return array */ private function _compileChilds($nodes, $parent_id = 1){ static $out = array(); foreach($nodes as $node){ if($node['parent_id'] == $parent_id){ $out[] = $node['id']; unset($nodes[ $node['id'] ]); $this->_compileChilds($nodes, $node['id']); } } return $out; } /** * Чистим кэш категорий или документов * * @param bool $category * @param bool $documents */ private function _clearCache($category = false, $documents = false){ if($category) $this->_cacher->write($this->_cacheKeyCategory, array(), null, 1); if($documents)$this->_cacher->write($this->_cacheKeyDocuments,array(), null, 1); } }
Дополнительные классы: DB и Cache PHP: $this->_cacher = Config::get('cacher'); $this->_cacher хранит в себе объект мемкеша. Методы read и write полностью идентичнны get и set в мемкеше. статик методы DB::select(), DB::selectRow() возвращает приведенные в массив поля таблиц. insert, update, delete просто помогают генерировать sql код на основе передаваемых данных. если необходимо то выложу дополнительные классы. но там совсем банальщина.
нет. не лучше. когда много запросов к файловой системе будет у тебя, все рухнет. поэтому у нас все шаблоны хранятся в бд. отдача гораздо выше (все в памяти хранится), доступ к файловой системе минимален.
таблицы типа MEMORY чтоли? дык а что мешает кэшировать конфиг в память? Суть в том, что для изменения конфигураций не нужно будет править скрипты.
Есть мнение что это преждевременная оптимизация... Т.е что сначала лучше хранить конфиг там где удобнее, а уже если будет нехватать, тогда уже перенести туда где будет быстрее.