Bug tracker. Преобразуем таблицу в html список (часть 4).

Владимир | | CodeIgniter, HTML, PHP, Web разработка.

bug_tracker_logo_part4

Сегодня я продолжаю рассказывать о разработке системы отслеживания ошибок. В этой части речь пойдет о создании списка багов и комментариев.

Напомню, в прошлый раз мы решили, что все для хранения записей о багах и комментариях к ним будем использовать две таблицы: bugs и comments.

Там же мы обсудили список полей и их назначение, и создали несколько внешних ключей для связи между таблицами.

Теперь переходим к созданию списка.

Чтобы лучше понять задачу рассмотрим html разметку, которую нам нужно получить.

<ul>
	<li>
		Описание бага №1
		<ul>
			<li>Комментарий 1</li>
			<li>
				Комментарий 2
				<ul>
					<li>Ответ на комментарий 2</li>
				</ul>
			</li>
		</ul>
	</li>
	<li>
		Описание бага №2
		<ul>
			<li>Комментарий 1 к багу 2</li>
			...
		</ul>
	</li>
</ul>

Как видите, разметка достаточно простая. Мы создаем список, каждый элемент которого соответствует записи о баге. Если к этому багу оставлены комментарии, то они размещаются во вложенном списке. Вкладывая такие списки друг в друга, мы формируем дерево комментариев.

Таким образом, нам нужно преобразовать данные из таблиц в дерево.

Примечание. Если вы интересуетесь хранением древовидных структур в базе данных, то советую почитать статью «Построение деревьев».

Теперь посмотрим, каким образом можно сформировать такой список. Есть несколько вариантов.

Можно решать задачу «в лоб». Т.е. первым запросом найти все записи о багах (напомню, у всех таких записей поле parent_id = NULL). А после этого в цикле отправлять запросы поиска комментариев к каждому из найденных багов. Недостаток такого подхода – большое количество запросов к БД.

Второй вариант. Мы можем получить всю необходимую информацию с помощью одного запроса. Но при этом усложняется алгоритм получения дерева с багами и комментариями.

Остановимся мы, конечно, на втором варианте.

И, прежде всего, нам нужно получить данные. Для этого создаем модель, назовем её mbug (application\models\mbugs.php), и метод getAllBugsWithComments.

class MBug extends Model {

function MBug() { parent::Model(); } /** * Ищет все баги и комментарии к ним. Баги возвращаются начиная с последнего добавленного. * * @return массив со всеми багами и комментариями к ним, FALSE - если ничего не найдено */ function getAllBugsWithComments() { $qGetAll = 'SELECT b.*, c.link, c.name AS category,' .' cm.id AS c_id, cm.uname AS c_uname, cm.description AS c_description,' .' cm.comment_date, cm.parent_id, cm.bug_id, cm.uemail AS c_uemail' .' FROM bugs AS b' .' LEFT JOIN categories AS c ON b.category_id=c.id' .' LEFT JOIN comments AS cm ON b.id=cm.bug_id' .' ORDER BY b.bug_date DESC'; $res = $this->db->query($qGetAll); if ($res->num_rows() <= 0) { return false; } return $res->result_array(); } }

Работает метод getAllBugsWithComments предельно просто. Он выполняет запрос к БД и возвращает результат его выполнения в виде массива.

Самая интересная часть – это, конечно, сам запрос. Для того, чтобы сформировать дерево, нам нужны данные о багах, сведения о категориях к которым они относятся и комментарии к ним.

Поэтому в запросе мы выполняем объединение трех таблиц (bugs, categories, comments). Рассмотрим, как выполняется это объединение.

Прежде всего, обратите внимание на то, что таблицы объединяются слева направо (LEFT JOIN). На первом этапе объединяются таблицы bugs и categories. Т.к. каждому багу соответствует только одна категория, то в результате мы получим исходную таблицу bugs с двумя новыми столбцами link и name из таблицы categories.

Обратите внимание, что т.к. таблица bugs расположена слева, то в результат войдут только те записи из таблицы categories, для которых есть соответствующая запись в таблице bugs. Другими словами, если у нас есть категория, в которой нет ни одного бага, то сведений о ней в результирующей таблице не будет.

На следующем этапе происходит объединение с таблицей comments. Тут возможны два варианта.

Первый – очередной баг не имеет комментариев. В этом случае в результирующей таблице будет одна строка со сведениями из таблиц bugs и categories, а все поля, соответствующие таблице comments будут иметь значения NULL.

Второй – очередной баг имеет один или более комментариев. Тогда в результирующей таблице будет одна или более строк с данными из таблиц bugs, categories и comments. Причем количество таких строк определяется количеством комментариев.

Посмотрите на пример такой таблицы (я опустил часть полей).

id title name c_description
1 Баг 1 Критические ошибки Комментарий к багу 1
2 Баг 2 Пожелания NULL
3 Баг 3 Критические ошибки Первый комментарий к багу 3
3 Баг 3 Критические ошибки Второй комментарий к багу 3

Переходим к преобразованию этой таблицы в HTML список.

Т.к. операция довольно сложная, то будет удобно написать для неё небольшую библиотеку Table2Tree.

Для этого создаём файл application\libraries\table2tree.php (название файла совпадает с названием библиотеки).

Но, прежде чем переходить к описанию библиотеки, рассмотрим пример её использования. Допустим, у нас есть контроллер (bugtracker) с методом page (формирует страницу со списком багов и комментариев).

class BugTracker extends Controller {

	//настройки отображения списка багов
	private $listConf = array(
		'commentOpen'=>'<li class="depth-{depth}">'
	);

	function BugTracker() {
		parent::Controller();

		$this->load->model('mbug');
		$this->load->library('Table2Tree');
	}

	function page($firstBug = 0) {
		...
		//загружаем общий список ошибок и комментариев к ним
		$bugs = $this->mbug->getAllBugsWithComments();
		
		if ($bugs !== false) {
			$bugsTree = $this->table2tree->getTree($bugs, $firstBug, $this->config->item('bugs_per_page'));
			
			$pageData['bugsList'] = $this->table2tree->getHTMLList($bugsTree, $this->listConf);
			
		}
		//формируем страницу
	}
}

Как видите, в конструкторе мы загрузили нашу библиотеку (Table2Tree) и модель (mbug).

Метод page() работает следующим образом.

1) Получаем список багов с комментариями (с помощью метода getAllBugsWithComments модели).

2) Преобразовываем полученную таблицу в многомерный массив (метод getTree нашей библиотеки).

3) Преобразовываем многомерный массив в HTML список (метод getHTMLList).

На втором и третьем этапе я хочу остановиться подробнее. Основной вопрос тут: что представляет собой многомерный массив и зачем он вообще нужен.

Дело в том, что мы формируем этот массив таким образом, что его структура полностью совпадает со структурой HTML списка который мы хотим получить.

Например, для списка, показанного в начале статьи, этот массив будет выглядеть примерно так.

[0]=>
	‘title’=>’Описание бага №1’
	...
	‘comments’=>
		[0]=>
			‘description’=>’Комментарий 1’
		[1]=>
			‘description’=>’Комментарий 2’
			‘comments’=>
				[0]=>
					‘description’=>’Ответ на комментарий 2’
[1]=>
	‘title’=>’Описание бага №2’
	...
	‘comments’=>
		[0]=>
			‘description’=>’Комментарий 1 к багу 2’

Используя такой массив, получить HTML список будет значительно проще.

Теперь рассмотрим, каким образом можно преобразовать результаты поиска по БД в такой массив.

Я решил разбить задачу на 2 этапа.

На первом – формируем массив с багами. В нем каждый элемент будет содержать описание бага и одномерный массив со всеми комментариями к нему.

На втором – преобразуем массивы с комментариями в многомерные (на основании значения в поле parent_id), т.е. формируем дерево комментариев.

Рассмотрим метод getTree

function getTree($data, $firstBug = 0, $count = 1) {
	$sTree = $this->_convert2SimpleTree($data);
	//запоминаем общее количество багов
	$this->bugsCount = count($sTree);
	//оставляем в массиве только те элементы, которые будут отображаться на странице
	$sTree = array_slice($sTree, $firstBug, $count, TRUE);
	foreach ($sTree as $i => $row) {
		if (!empty($row['comments'])) {
			$sTree[$i]['comments'] = $this->_nestedComments($row['comments'], null, 0);
		}
	}
	return $sTree;
}

Он имеет 3 входных параметра:
data – массив с результатами поиска в БД;
firstBug – номер первого бага на странице (используется для пагинации, нет никакого смысла строить дерево комментариев для багов, которые не будут отображаться на странице);
count – количество багов на странице.

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

После этого мы оставляем в массиве только те записи о багах, которые будут отображаться на странице (строка 6).

И формируем дерево комментариев. Для этого мы проходим в цикле по всем элементам массива и с помощью метода _nestedComments преобразовываем массив comments в многомерный.

Рассмотрим метод _convert2SimpleTree

function _convert2SimpleTree($bugs) {
	$bugRes = array();
	$currentBugId = 0;
	$i = -1;
	$j = 0;
	foreach ($bugs as $bug) {
		if ($bug['id'] != $currentBugId) {
			$currentBugId = $bug['id'];
			$i++;
			//заполняем массив данными о баге
			$bugRes[$i]['id'] = $bug['id'];
			$bugRes[$i]['title'] = $bug['title'];
			$bugRes[$i]['uname'] = $bug['uname'];
			$bugRes[$i]['category_id'] = $bug['category_id'];
			$bugRes[$i]['description'] = $bug['description'];
			$bugRes[$i]['status'] = $bug['status'];
			$bugRes[$i]['bug_date'] = $bug['bug_date'];
			$bugRes[$i]['uemail'] = $bug['uemail'];

			//данные о категории
			$bugRes[$i]['link'] = $bug['link'];
			$bugRes[$i]['category'] = $bug['category'];
			
			//создаем массив с комментариями
			if ($bug['c_id'] != NULL) {
				$bugRes[$i]['comments'] = array();
				$j = 0;
				$bugRes[$i]['comments'][$j]['id'] = $bug['c_id'];
				$bugRes[$i]['comments'][$j]['uname'] = $bug['c_uname'];
				$bugRes[$i]['comments'][$j]['description'] = $bug['c_description'];
				$bugRes[$i]['comments'][$j]['comment_date'] = $bug['comment_date'];
				$bugRes[$i]['comments'][$j]['parent_id'] = $bug['parent_id'];
				$bugRes[$i]['comments'][$j]['bug_id'] = $bug['bug_id'];
				$bugRes[$i]['comments'][$j]['uemail'] = $bug['c_uemail'];
				$j++;
			}
		}
		else {
			//добавляем новую запись в массив с комментариями
			$bugRes[$i]['comments'][$j]['id'] = $bug['c_id'];
			$bugRes[$i]['comments'][$j]['uname'] = $bug['c_uname'];
			$bugRes[$i]['comments'][$j]['description'] = $bug['c_description'];
			$bugRes[$i]['comments'][$j]['comment_date'] = $bug['comment_date'];
			$bugRes[$i]['comments'][$j]['parent_id'] = $bug['parent_id'];
			$bugRes[$i]['comments'][$j]['bug_id'] = $bug['bug_id'];
			$bugRes[$i]['comments'][$j]['uemail'] = $bug['c_uemail'];
			$j++;
		}
	}
	return $bugRes;
}

Выглядит он довольно объемным, но большую часть его составляют операторы присвоения. А принцип работы достаточно простой.

В исходной таблице (с результатами из БД) каждому багу может соответствовать несколько строк (в зависимости от количества комментариев).

Поэтому мы перебираем в цикле все строки этой таблицы и если встречаем новый баг (его id отличается от предыдущего), то создаём новый элемент в результирующем массиве и копируем в него данные бага, категории и комментария (если он есть).

Если очередная строка относится к той же записи о баге, что и предыдущая, то мы копируем только данные комментария.

Продолжение на следующей странице >>

Страница: 1 2

  • Big_Shark

    Что та очень сложно как то получилось.
    Выбирать все 1 запросом не удобно так как я считаю что для 1 таблицы БД должна быть 1 модель.
    Вызывать комментарии мы будем лишь на просмотре полной версии текста про баг так что думаю Проблем со скоростью при разбиении запросов тоже не должно возникнуть.
    Смешивать все баги и комменты в 1 массив тоже достаточно не удобно мне показалось я бы сделал 2 массива $bug и $comment
    и по $bug['id'] вытаскивал бы комменты типа так $comment[$bug['id']].
    Очень ваш стильнаписания разница с моим так что будет интересно посмотреть на то что из этого выйдет.

  • Big_Shark

    Что та очень сложно как то получилось.
    Выбирать все 1 запросом не удобно так как я считаю что для 1 таблицы БД должна быть 1 модель.
    Вызывать комментарии мы будем лишь на просмотре полной версии текста про баг так что думаю Проблем со скоростью при разбиении запросов тоже не должно возникнуть.
    Смешивать все баги и комменты в 1 массив тоже достаточно не удобно мне показалось я бы сделал 2 массива $bug и $comment
    и по $bug['id'] вытаскивал бы комменты типа так $comment[$bug['id']].
    Очень ваш стильнаписания разница с моим так что будет интересно посмотреть на то что из этого выйдет.

  • Peter

    Проще было бы получать отдельными запросами списки комментариев для каждого бага и сразу же вызывать для каждого из них $this->CI->parser->parse, не занося их в промежуточный многомерный массив.

    Чтобы построить дерево комментариев, можно извлекать из базы комментарии, являющиеся потомками текущего комментария, как это сделано в статье http://www.sitepoint.com/hierarchical-data-database/ (см. функцию display_children)

  • Peter

    Проще было бы получать отдельными запросами списки комментариев для каждого бага и сразу же вызывать для каждого из них $this->CI->parser->parse, не занося их в промежуточный многомерный массив.

    Чтобы построить дерево комментариев, можно извлекать из базы комментарии, являющиеся потомками текущего комментария, как это сделано в статье http://www.sitepoint.com/hierarchical-data-database/ (см. функцию display_children)

  • Peter

    Извините за «битую» ссылку. Вот правильная: http://www.sitepoint.com/print/hierarchical-data-database/

  • Peter

    Извините за «битую» ссылку. Вот правильная: http://www.sitepoint.com/print/hierarchical-data-database/

  • Польза сомнительно. Притом трудоёмко.

  • Польза сомнительно. Притом трудоёмко.

  • Я так понял, ты выбираешь из базы все дерево (все баги и все комменты, к ним привязанные), а уже на этапе построения дерева отрезаешь slice`ом то, что нужно для пейджера… А если база будет очень большая? Зачем нам столько лишних данных?

    Логичнее было бы реализовывать по страничное отображение через введение лимита в запрос на базу данных, не вытягивая при этом ни лишних багов, ни лишних комментариев.

    По своему опыту скажу, что лучше вытягивать сначала список по основной таблице, а уже потом по зависящей. Т.е. сначала тянем баги, потом по каждый баг комментарии. Такое функциональное разделение может потом сильно пригодиться. Например, если тебе нужно будет переформатировать список (сделать выборку по определенным условиям). Или скажем такая задача — организовать рсс ленту новых багов (просто их заголовков). Твое решение слишком «неповоротливо» для таких целей.

    В общем, дело стиля.. Я на ZendFW работаю, может в CI действительно удобнее по-другому.

  • Я так понял, ты выбираешь из базы все дерево (все баги и все комменты, к ним привязанные), а уже на этапе построения дерева отрезаешь slice`ом то, что нужно для пейджера… А если база будет очень большая? Зачем нам столько лишних данных?

    Логичнее было бы реализовывать по страничное отображение через введение лимита в запрос на базу данных, не вытягивая при этом ни лишних багов, ни лишних комментариев.

    По своему опыту скажу, что лучше вытягивать сначала список по основной таблице, а уже потом по зависящей. Т.е. сначала тянем баги, потом по каждый баг комментарии. Такое функциональное разделение может потом сильно пригодиться. Например, если тебе нужно будет переформатировать список (сделать выборку по определенным условиям). Или скажем такая задача — организовать рсс ленту новых багов (просто их заголовков). Твое решение слишком «неповоротливо» для таких целей.

    В общем, дело стиля.. Я на ZendFW работаю, может в CI действительно удобнее по-другому.

  • Сразу поясню, почему я использовал один запрос. Идея в том, что на страницах баг трекера всегда должны отображаться и баги и комментарии. После загрузки страницы все комментарии будут автоматически сворачиваться (с помощью JS) и посетитель увидит только заголовки. При клике по заголовку список комментариев будет развернут.
    Это должно увеличить скорость работы со страницей, т.к. подгружать комментарии с помощью отдельных запросов не придется.

    Разбить на 2 массива таблицу можно, но не уверен, что такой вариант был бы намного проще. В этом случае пришлось бы:
    1) для каждого бага найти соответствующие ему комментарии во втором массиве;
    2) с помощью рекурсивной функции построить деревья для каждой группы комментариев.

    Вариант, который приведен в статье на sitepoint.com, конечно, выглядит гораздо красивее и короче. Но при этом возникает ситуация, которой я хотел избежать. Автор той статьи предлагает выполнять запросы внутри рекурсивной функции, а это означает, что если у нас есть десять вложенных комментариев, то для их получения будет выполнено 10 запросов. Т.е. может получиться, что мы будем «тянуть» из базы комментарии по одному.

    Не думаю, что между ZendFW и CI в этом плане есть разница. Во всяком случае CI не накладывает своих ограничений на работу с БД. И я конечно не предлагаю использовать такой запрос для страниц на которых не нужны комментарии. Этот пример касается только страницы с общим списком. Для формирования RSS ленты или страницы с отдельным багом я добавлю дополнительный метод в модель, который будет получать, например, только заголовки багов из БД.

    >> Зачем нам столько лишних данных?

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

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

    Хотя, может быть я тут не прав. Нужно потестировать скорость выполнения этих вариантов.

  • Сразу поясню, почему я использовал один запрос. Идея в том, что на страницах баг трекера всегда должны отображаться и баги и комментарии. После загрузки страницы все комментарии будут автоматически сворачиваться (с помощью JS) и посетитель увидит только заголовки. При клике по заголовку список комментариев будет развернут.
    Это должно увеличить скорость работы со страницей, т.к. подгружать комментарии с помощью отдельных запросов не придется.

    Разбить на 2 массива таблицу можно, но не уверен, что такой вариант был бы намного проще. В этом случае пришлось бы:
    1) для каждого бага найти соответствующие ему комментарии во втором массиве;
    2) с помощью рекурсивной функции построить деревья для каждой группы комментариев.

    Вариант, который приведен в статье на sitepoint.com, конечно, выглядит гораздо красивее и короче. Но при этом возникает ситуация, которой я хотел избежать. Автор той статьи предлагает выполнять запросы внутри рекурсивной функции, а это означает, что если у нас есть десять вложенных комментариев, то для их получения будет выполнено 10 запросов. Т.е. может получиться, что мы будем «тянуть» из базы комментарии по одному.

    Не думаю, что между ZendFW и CI в этом плане есть разница. Во всяком случае CI не накладывает своих ограничений на работу с БД. И я конечно не предлагаю использовать такой запрос для страниц на которых не нужны комментарии. Этот пример касается только страницы с общим списком. Для формирования RSS ленты или страницы с отдельным багом я добавлю дополнительный метод в модель, который будет получать, например, только заголовки багов из БД.

    >> Зачем нам столько лишних данных?

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

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

    Хотя, может быть я тут не прав. Нужно потестировать скорость выполнения этих вариантов.

  • Владимир, вот это очень сомнительно:

    >>После загрузки страницы все комментарии будут автоматически >>сворачиваться (с помощью JS) и посетитель увидит только >>заголовки. При клике по заголовку список комментариев будет >>развернут.
    >>Это должно увеличить скорость работы со страницей, т.к. >>подгружать комментарии с помощью отдельных запросов не >>придется.

    Я когда-то работал с поисковой системой, которая отображала данные в стиле: «Тайтл + при клике на тайтл — дополнительные данные». Я тоже сначала сделал вывод всего, а потом «схлопывание» всего кроме тайтлов. Страничка грузиться реально долго при этом, потому что объемы текстовых данных большие (хоть они и не видны).

    Эту проблему и проблему с пейджингом можно решить с помощью AJAX — показывает названия багов (и описание по желанию) с постраничной разбивкой. Под этим пишем «Комментарии (Н штук)». При клике на ссылку «Комментарии» посылаем AJAX запрос, получаем список комментариев (дерево), отображаем его и ВНУТРЕННИЙ ПЕЙДЖЕР для комментариев, если их сильно много. При чем этот пейджер работает также на AJAXе.

    Т.е. у багов свое постраничное отображение, у комментов свое.

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

    • >> это очень сомнительно

      Хотел привести несколько доводов в защиту своего варианта, но, похоже, вы правы. Я думал только о текстовом варианте страницы, она грузится быстро. Но как только в комментариях появятся картинки о быстрой загрузке можно будет забыть.

      Кстати, я нашел способ получить все данные одним запросом, но с ограничением по количеству комментариев.

      SELECT b. * , c.link, c.name AS category, cm.id AS c_id,
      cm.uname AS c_uname, cm.description AS c_description,
      cm.comment_date, cm.parent_id, cm.bug_id, cm.uemail AS c_uemail
      FROM comments AS cm
      RIGHT JOIN (
      SELECT * FROM bugs
      LIMIT 0 , 2
      ) b ON b.id = cm.bug_id
      LEFT JOIN categories AS c ON b.category_id = c.id
      ORDER BY b.bug_date DESC

  • Владимир, вот это очень сомнительно:

    >>После загрузки страницы все комментарии будут автоматически >>сворачиваться (с помощью JS) и посетитель увидит только >>заголовки. При клике по заголовку список комментариев будет >>развернут.
    >>Это должно увеличить скорость работы со страницей, т.к. >>подгружать комментарии с помощью отдельных запросов не >>придется.

    Я когда-то работал с поисковой системой, которая отображала данные в стиле: «Тайтл + при клике на тайтл — дополнительные данные». Я тоже сначала сделал вывод всего, а потом «схлопывание» всего кроме тайтлов. Страничка грузиться реально долго при этом, потому что объемы текстовых данных большие (хоть они и не видны).

    Эту проблему и проблему с пейджингом можно решить с помощью AJAX — показывает названия багов (и описание по желанию) с постраничной разбивкой. Под этим пишем «Комментарии (Н штук)». При клике на ссылку «Комментарии» посылаем AJAX запрос, получаем список комментариев (дерево), отображаем его и ВНУТРЕННИЙ ПЕЙДЖЕР для комментариев, если их сильно много. При чем этот пейджер работает также на AJAXе.

    Т.е. у багов свое постраничное отображение, у комментов свое.

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

    • >> это очень сомнительно

      Хотел привести несколько доводов в защиту своего варианта, но, похоже, вы правы. Я думал только о текстовом варианте страницы, она грузится быстро. Но как только в комментариях появятся картинки о быстрой загрузке можно будет забыть.

      Кстати, я нашел способ получить все данные одним запросом, но с ограничением по количеству комментариев.

      SELECT b. * , c.link, c.name AS category, cm.id AS c_id,
      cm.uname AS c_uname, cm.description AS c_description,
      cm.comment_date, cm.parent_id, cm.bug_id, cm.uemail AS c_uemail
      	FROM comments AS cm
      	RIGHT JOIN (
      	SELECT * FROM bugs
      	LIMIT 0 , 2
      	) b ON b.id = cm.bug_id
      	LEFT JOIN categories AS c ON b.category_id = c.id
      	ORDER BY b.bug_date DESC
  • Big_Shark

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

    Каким запросом в цикле вы о чем?
    Все делается очень просто получаем список ID багов и после этого делаем запрос к таблице с комментарием примерное так
    $this->db->where_in('bug_id', $array_bug_id);
    После чего получившейся массив уже рекурсивно превращаем в дерево.
    Лучше подгружать комментарии ajaxом и кэшировать полученное дерево через сразу показывать все комментарии на страницы и скрывать их так как размер страницы увелчиваеться очень сильно.

    • >> $this->db->where_in('bug_id', $array_bug_id);

      Согласен, так значительно лучше 😉 Но я написал «потом отдельно комментариев для каждого бага». А вашим запросом будут вытянуты сразу все комментарии.

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

      • Big_Shark

        А вашим запросом будут вытянуты сразу все комментарии.

        Все комментарии для списка багов.

        Интересно уже прочитать следующею статью)

  • Big_Shark

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

    Каким запросом в цикле вы о чем?
    Все делается очень просто получаем список ID багов и после этого делаем запрос к таблице с комментарием примерное так
    $this->db->where_in('bug_id', $array_bug_id);
    После чего получившейся массив уже рекурсивно превращаем в дерево.
    Лучше подгружать комментарии ajaxом и кэшировать полученное дерево через сразу показывать все комментарии на страницы и скрывать их так как размер страницы увелчиваеться очень сильно.

    • >> $this->db->where_in('bug_id', $array_bug_id);

      Согласен, так значительно лучше 😉 Но я написал «потом отдельно комментариев для каждого бага». А вашим запросом будут вытянуты сразу все комментарии.

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

      • Big_Shark

        А вашим запросом будут вытянуты сразу все комментарии.

        Все комментарии для списка багов.

        Интересно уже прочитать следующею статью)

  • Peter

    В упомянутой статье приводится ещё один алгоритм (Modified Preorder Tree Traversal Algorithm). С его помощью можно получить иерархический список комментариев одним запросом (не нужно строить дерево в PHP-коде). Если в багтрекере будут глубоко вложенные деревья комментариев, то этот метод должен работать быстрее, чем adjacency list, который Вы используете.

    Другая статья о том же алгоритме: http://dev.mysql.com/tech-resources/articles/hierarchical-data.html (здесь решение на чистом SQL, без PHP).

    Имхо, лучшее решение — извлекать список комментариев отдельным запросом для каждого бага и использовать preorder tree traversal для вывода комментов. Тогда алгоритм будет оптимальным, и при необходимости можно будет легко перейти к AJAX, как предлагает Алексей.

    P.S. Спасибо за интересный блог.

    • В комментариях к прошлой части немного обсуждали эту тему (использование Nested Set Model). Мое мнение — это довольно сложный вариант. Приводить код без пояснений я не хочу, а если начать рассказывать о Nested Set, то вместо цикла о разработке баг трекера, получится цикл о принципах работы с Nested Set 🙂

  • Peter

    В упомянутой статье приводится ещё один алгоритм (Modified Preorder Tree Traversal Algorithm). С его помощью можно получить иерархический список комментариев одним запросом (не нужно строить дерево в PHP-коде). Если в багтрекере будут глубоко вложенные деревья комментариев, то этот метод должен работать быстрее, чем adjacency list, который Вы используете.

    Другая статья о том же алгоритме: http://dev.mysql.com/tech-resources/articles/hierarchical-data.html (здесь решение на чистом SQL, без PHP).

    Имхо, лучшее решение — извлекать список комментариев отдельным запросом для каждого бага и использовать preorder tree traversal для вывода комментов. Тогда алгоритм будет оптимальным, и при необходимости можно будет легко перейти к AJAX, как предлагает Алексей.

    P.S. Спасибо за интересный блог.

    • В комментариях к прошлой части немного обсуждали эту тему (использование Nested Set Model). Мое мнение — это довольно сложный вариант. Приводить код без пояснений я не хочу, а если начать рассказывать о Nested Set, то вместо цикла о разработке баг трекера, получится цикл о принципах работы с Nested Set 🙂

  • используем метод описанный петером. работает.

    • Не сомневаюсь 😉 Nested Set — это одно из типовых решений.

  • используем метод описанный петером. работает.

    • Не сомневаюсь 😉 Nested Set — это одно из типовых решений.

  • Nested Set Model — в принципе, если вникнуть, то очень удобно

  • Nested Set Model — в принципе, если вникнуть, то очень удобно

  • Занимательно, спасибо.

  • Занимательно, спасибо.

  • Действительно, в два запроса, пожалуй будет лучше:
    1. запрос — баги с пагинацией
    2. запрос — все комментарии к этим багам

    По поводу Ajax-подгрузки — обсуждалось про индексацию в первой статье цикла.

  • Shal

    Спасибо! Немного сложная, но рабочая реализация!

  • Tester_grep

    Пользуюсь этим фреймворком уже несколько лет. Удобная справка и вообще хорошо документирован.
    Кстати, появилось руководство пользователя на русском для версии 2.0.0
    http://codeigniter.su

  • Tester_grep

    Пользуюсь этим фреймворком уже несколько лет. Удобная справка и вообще хорошо документирован.
    Кстати, появилось руководство пользователя на русском для версии 2.0.0
    http://codeigniter.su