Недавно я сделал нечто похожее, используя один запрос и один цикл while. Он использует ссылки для построения древовидных структур данных (массив) с помощью плоской (массив). Там нет SPL, потому что я не чувствовал в этом необходимости. На GitHub есть суть с лучшей цветовой схемой:)
/**
* Each element in the return array has a 'data' key, holding category data,
* like name, and a 'children' key holding its subcategories.
*
* @param resource $resource MySQL resource resulted from mysql_query
* @param string $id_key Name of the 'id' field
* @param string $parent_id_key Name of the 'parent_id' field
* @param boolean $use_cache Use cached result from previous calls. Defaults to TRUE
* @return array
*/
function categories($resource, $id_key, $parent_id_key, $use_cache = true) {
// Cache the categories in a static local variable. This way, the query
// will be executed just for the first function call. Subsequent calls
// will return imediatelly, unless you tell it not to.
static $tree = array();
if ($tree && $use_cache) {
return $tree;
}
// Flat representation of the categories for fast retrieval using array
// keys. Each element will be referenced in the $tree array. This
// allows to build a tree data structure using a flat one.
$flat = array();
// Reset the $tree, in case $use_cache=false in a subsequent call
$tree = array();
while ($row = mysql_fetch_object($resource)) {
$flat[$row->$id_key] = array(
'data' => $row,
'children' => array(),
);
if (array_key_exists($row->$parent_id_key, $flat)) {
// Assign children by reference so that possible subcategories of
// this one will appear in the tree structure ($tree)
$flat[$row->$parent_id_key]['children'][] =& $flat[$row->$id_key];
}
if ($row->$parent_id_key == 0) {
// Assign by reference for synchronizing $flat with $tree;
$tree[] =& $flat[$row->$id_key];
}
}
return $tree;
}
Также функция отделена от структуры базы данных. Вам нужно передать ему ресурс mysql_query, строку, представляющую поле id, и строку, представляющую поле parent_id. Плохая часть заключается в том, что он связан с расширением PHP mysql, поскольку использует вызов mysql_fetch_object. Возможно, это можно улучшить.
Некоторым другим преимуществом является то, что он кэширует результат для последующих вызовов, если только вы не скажете ему аннулировать кэш, который является четвертым (логическим) параметром.
Посмотрите, поможет ли это вам.