2002-11-18 17:08:43

Иногда может понадобиться список всех URI сайта(например для создания навигационной строки). Можно хранить этот список в текстовом файле, однако это неудобно, например при изменении имени каталога, который имеет детей(подкаталоги), придётся менять все записи являющиеся детями этого раздела и при глубокой иерархии это может привести к большим проблемам.

Я предлагаю хранить информацию о разделах в БД, в таблице следующей структуры(t_sections):

  • id - числовой(уникальный) идентификатор раздела
  • parent_id - идентификатор раздела родителя(если нет, то он равен нулю)
  • dir - имя каталога раздела (без слешей)
  • title - название раздела

Пример:

id parent_id dir title
1 0 hardware Железо
11 1 computers Компьютеры
12 1 printers Принтеры
121 12 laser Лазерные
122 12 ink Струйные
2 0 software Программное обеспечение
21 2 os Операционные системы
22 2 editors Текстовые редакторы
3 0 news Новости

И далее по информации из этой таблицы формируется список всех URI, - это удобно тем, что при изменении любого имени каталога раздела имеющего детей, вам не нужно будет менять никакие другие записи, кроме самой изменяемой записи раздела. Разумеется идентификатор раздела не должен меняться ни при каких условиях.

Код класса который формирует таблицу всех URI сайта:

#######
# работа с URI - формирование списка всех URI сайта
@CLASS
uri

#######
# конструктор
@init[]
$items[^table::sql{
		SELECT
			id,
			parent_id,
			dir,
			title
		FROM
			t_sections
}]

#######
# форм. таблицы соответствий uri для всех разделов сайта
@get_all_site_uri[]
$result[^table::create{uri	id	title}]
^items.menu{
	^result.append{^get_uri[$items.id]	$items.id	$items.title}
}

#######
# получение uri раздела по заданному id
@get_uri[id][item;uri;define_parent_id;parent_id;method_id]
$method_id($id)
# получение parent_id раздела по заданному id
$define_parent_id{
	$item[^items.select($items.id == $method_id)]
	$parent_id($item.parent_id)
}
$define_parent_id
^while($parent_id){
	$uri[$item.dir/$uri]
	$method_id($item.parent_id)
	$define_parent_id
}
$uri[$item.dir/$uri]
$result[/$uri]

Код запускается в работу так:

$object[^uri::init[]]
# получаем таблицу содержащую список всех URI сайта
$site_uri[^object.get_all_site_uri[]]

После бурных дебатов с товарищами по несчастью, родился ещё один вариант кода для данной задачи:

#######
# работа с URI - формирование списка всех URI сайта
@CLASS
uri

#######
# конструктор
@init[]
$items[^table::sql{
		SELECT
			id,
			parent_id,
			dir,
			title
		FROM
			t_sections
}]

#######
# форм. таблицы соответствий uri для всех разделов сайта
@get_all_site_uri[]
$result[^table::create{id	title	uri}]
^items.menu{
	^result.append{$items.id	$items.title	^get_uri[$items.id]}
}

#######
# получение uri раздела по заданному id
@get_uri[id][parent_id;uri]
$parent_id($id)
^while($parent_id && ^items.locate[id;$parent_id]){
	$uri[$items.dir/$uri]
	$parent_id($items.parent_id)
}
$result[/$uri]

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

Во втором случае ресурсов потребляется меньше ибо ^table.locate[...] очевидно быстрее и не выполняется ненужных проходов по таблице. Однако здесь очень важен порядок вызова метода ^get_uri[] в:

^result.append{$items.id	$items.title	^get_uri[$items.id]}

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

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

Идем далее. Есть ещё один метод, - это использование хэша (придумал не я). Поиск с помощью хэша работает побыстрее и у него отсутствет недостаток заключающийся в порядке вызова. Код с использованием хэша:

#######
# работа с URI - формирование списка всех URI сайта
@CLASS
uri

#######
# конструктор
@init[]
$items[^table::sql{
		SELECT
			id,
			parent_id,
			dir,
			title
		FROM
			t_sections
}]

# создание хэша из таблицы для ускорения поиска
$items_hash[^items.hash[id]]

#######
# форм. таблицы соответствий uri для всех разделов сайта
@get_all_site_uri[][uri]
$result[^table::create{uri	id	title}]
$uri{^if(def $items_hash.[$items.id].uri){$items_hash.[$items.id].uri}{^get_uri[$items.id]}}
^items.menu{
	^result.append{$uri	$items.id	$items.title}
}

#######
# получение uri раздела по заданному id
@get_uri[id][parent_id;uri]
$parent_id($id)
^while($parent_id && $items_hash.[$parent_id]){
	$uri[$items_hash.[$parent_id].dir/$uri]
	$items_hash.[$id].uri[$uri]
	$parent_id($items_hash.[$parent_id].parent_id)
}
$result[/$uri]

Да, и тут опять произошли бурные дебаты с товарищами, - выяснилось что база данных вовсе не обязательна, - информацию о разделах можно хранить в простом текстовом, tab-delimited файле с указанной структурой. В этом случае достаточно слегка изменить реализацию конструктора:

#######
# конструктор
@init[]
$items[^table::load[sections.txt]

# создание хэша из таблицы для ускорения поиска
$items_hash[^items.hash[id]]

Где в sections.txt и хранится вся информация о разделах.

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

#######
# работа с URI - формирование списка всех URI сайта
@CLASS
uri

#######
# конструктор
@init[items]
# входная таблица разделов
$self.items[$items]

# создание хэша из таблицы для ускорения поиска
$items_hash[^items.hash[id]]

#######
# форм. таблицы соответствий uri для всех разделов сайта
@get_all_site_uri[][uri]
$result[^table::create{uri	id	title}]
$uri{^if(def $items_hash.[$items.id].uri){$items_hash.[$items.id].uri}{^get_uri[$items.id]}}
^items.menu{
	^result.append{$uri	$items.id	$items.title}
}

#######
# получение uri раздела по заданному id
@get_uri[id][parent_id;uri]
$parent_id($id)
^while($parent_id && $items_hash.[$parent_id]){
	$uri[$items_hash.[$parent_id].dir/$uri]
	$items_hash.[$id].uri[$uri]
	$parent_id($items_hash.[$parent_id].parent_id)
}
$result[/$uri]

Здесь перед формированием объекта класса, необходимо сначала сформировать исходную таблицу разделов, которую и передавать в качестве параметра конструктору, например:

# создание исходной таблицы
$items[^table::sql{
		SELECT
			id,
			parent_id,
			dir,
			title
		FROM
			t_sections
}]

# создание объекта класса
$object[^uri::init[$items]]

# получаем таблицу содержащую список всех URI сайта
$site_uri[^object.get_all_site_uri[]]

Как выяснилось ещё позднее, - в последних двух примерах (с хэшем) есть неточности:

  1. В методе ^get_all_site_uri[] условие def $items_hash.[$items.id].uri никода не выполняется, если до этого ни разу не вызван метод ^get_uri[] не в контексте этого метода - поэтому, этот наворот оттуда убран. Вообще говоря, это задумывалось для повторного использования уже вычисленных полных URI, которые, если они являются родителями других разделов, могли бы использоваться при вычислении URI детей.
  2. $items_hash.[$id].uri[$uri] - в методе ^get_uri[] формирует полный URI без начального слэша.

Исправляю данные неточности (код класса с комментариями):

#######
@CLASS
uri

#######
# конструктор
@init[items]
# входная таблица разделов
$self.items[$items]

# создание хэша из таблицы для ускорения поиска
$items_hash[^items.hash[id]]

#######
# форм. таблицы соответствий uri для всех разделов сайта
@get_all_site_uri[]
$result[^table::create{uri	id	title}]
^items.menu{
	^result.append{^get_uri[$items.id]	$items.id	$items.title}
}

#######
# получение uri раздела по заданному id
@get_uri[id][parent_id;uri]
$parent_id($id)
^while($parent_id && $items_hash.[$parent_id]){
# Если для родителя уже вычислен URI, нефиг крутить весь цикл -
# сначала запросить $items_hash на предмет возможно сформированных полных URI родителей
# и затем, просто подставить его перед $uri - промежуточной переменной
# через которую формируется путь, путём добавления в её начало
# частей пути от родителей.
	^if(def $items_hash.[$parent_id].uri){
		$uri[${items_hash.[$parent_id].uri}$uri]
		$items_hash.[$id].uri[$uri]
		$parent_id(0)
# внимание! результат должен формироваться именно так (без начального слеша)
# потому что начальный слеш уже есть - он добавлен при формировании
# URI родителя, см. альтернативную ветку
		$result[$uri]
	}{
		$uri[$items_hash.[$parent_id].dir/$uri]
# формируем новый ключ хеша (поле класса) - полный URI некоего раздела
# необходимо для повторного использования уже вычисленных URI разделов
		$items_hash.[$id].uri[/$uri]
		$parent_id($items_hash.[$parent_id].parent_id)	
	}
}
# вывод только если результат неопределён т.е.
# условие if внутри цикла ложно - формирование URI для
# разделов для которых ещё не вычислено полное URI родителя
# для проверки повторного использования вычисленных URI
# можно закомментировать if - в случае повторного использования
# URI при повторном использовании будет с двумя начальными слешами
^if(!def $result){
	$result[/$uri]
}
Небольшое примечание:
Повторно, уже вычисленные URI родителей, будут использоваться только тогда, когда их вычисление (вызов ^get_uri[]) происходит перед вычислением URI детей, т.е. в случае работы метода ^get_all_site_uri[] хорошо бы, чтобы в исходной таблице разделов, родители шли раньше детей.

В итоге, после всех манипуляций, для приведенного примера таблицы, получается следующий список URI:

uri id title
/hardware/ 1 Железо
/hardware/computers/ 11 Компьютеры
/hardware/printers/ 12 Принтеры
/hardware/printers/laser/ 121 Лазерные
/hardware/printers/ink/ 122 Струйные
/software/ 2 Программное обеспечение
/software/os/ 21 Операционные системы
/software/editors/ 22 Текстовые радакторы
/news/ 3 Новости

2002-11-18 17:08:43  parser snippet web
comments powered by Disqus