Na pewno nie raz okazuje się, że na stronie czy w innej aplikacji trzeba umieszczać i zarządzać danymi hierarchicznymi. Jeżeli korzystamy z Doctrine’a to mamy do dyspozycji NestedSet - bardzo przydatne narzędzie
Zaczynamy
Po pierwsze określamy strukturę tabeli dla danych hierarchicznych:
App_Menus:
actAs:
NestedSet:
hasManyRoots: true
rootColumnName: parent_id
tableName: menus
columns:
id:
type: integer
primary: true
autoincrement: true
name: string(64)
type: integer
Powyżej mamy definicję tabeli w której może występować wiele drzeni drzewa, a pole określające dane drzewo nazwane zostało parent_id (w dokumentacji Doctrine, używają root_id jednak w moim przypadku z racji zaszłości historychnych wolę parent_id)
Dzięki temu wpisowi orzymujemy takiego SQL’q:
CREATE TABLE `t_menus` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(64) DEFAULT NULL,
`parent_id` bigint(20) DEFAULT NULL,
`type` bigint(20) DEFAULT NULL,
`lft` int(11) DEFAULT NULL,
`rgt` int(11) DEFAULT NULL,
`level` smallint(6) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
Implementacja:
Dobra wszystko fajnie, ale jak to teraz używać?
Nie ma nic prostrzego, zakładam używanie smartów i to wersji 3, zdaje sobie sprawę że Smarty 3 nie doczekały się jeszcze dobrej dokumentacji, ale zawsze jest kod systemu szablonów - można poczytać :)
Po pierwsze wyciągamy dane z bazy:
public function getTreeFromRoot
()
{
$treeObject = Doctrine_Core::
getTable(‘Model_App_Menus’)->
getTree();
$rootColumnName =
$treeObject->
getAttribute(‘rootColumnName’);
foreach ($treeObject->
fetchRoots() as $root)
{
$options =
array(‘root_id’ =>
$root->
$rootColumnName);
return $treeObject->
fetchTree($options)->
toHierarchy()->
toArray();
}
}
Wynikiem jest tablica wielowymiarowa z zależnościami
Dla ułatwienia sobie wykorzystania tejże tablicy w systemie szablonów deko sobie poczyśćmy wynik, chodzi głównie o to, że tablica z Doctrine’a zawsze zawiera element tablicowy __children nawet jeżeli jest on pusty.
Trywialna funkcja wywala nam puste tablice:
public function flatArray
($array)
{
foreach ($array as $key =>
$value)
{
if(is_array($value))
{
if(count($value) !=
0) $out[$key] =
$this->
flatArray($value);
}
else
{
$out[$key] =
$value;
}
}
return $out;
}
Wynik możemy przekazać do Smartów i wyświetlić za pomocą małej rekurencji:
{function name=menu level=0}
{strip}
{foreach $data as $fields}
<ul class="{if $level eq "0"}sortable{/if}">
{foreach from=$fields item=field key=key}
{if $level neq "0"}
{if $key eq "id"}
<li id="list_{$field}">{assign var="ids" value=$field}{/if}
{if $key eq "name"}
<div class="ekgreybox">{$field} (lorem ipsum…)
</div>{/if}
{if $key eq "level"}{if $fields|@count eq ‘7′}
</li>{/if}{/if}
{/if}
{if $key eq "__children"}
{menu data=$field level=$level+1}
{if $level neq "0"}
</li>{/if}
{/if}
{/foreach}
</ul>
{/foreach}
{/strip}
{/function}
{menu data=$childs}
Dzięki temu otrzymamy ładne rzewko w liście.
Zarządzanie:
Budujemy ładną aplikację i chcemy mieć drag-n-drop’owe określenie menu, fajnie ale jak?
Najszybciej :)
Ja jestem strasznie leniwy i średnio lubię javascript’a, więc korzystam z gotowców :) Trzeba zassać sobie mały kodzik do drag-n-dropowego zarządzania drzewami:
Tutaj….
Fajnie działa, jednak zwraca mało ciekawy wynik, zobacz stronę demo ;)
Nie ma problemu, za pomocą Ajaxa obsługujemy i to (funkcja jeszcze nie zoptymalizowana, ale działa):
public function sortmenuAction
()
{
$root_node =
$_POST[‘menu_id’];
$pola =
$_POST[‘list’];
$childs =
0;
foreach ($_POST[‘list’] as $key =>
$value)
{
$run=
0;
if($childs !=
0)
{
$run =
1;
$childs–;
$parent_pos =
$key -
1;
while (strstr($pola[$parent_pos],
"_") ==
false){
$parent_pos–;
}
$tmp =
explode("_",
$pola[$parent_pos]);
$data =
$tmp[0];
if (strstr($value,
"_") !=
false) {
$tmp =
explode("_",
$value);
$dzieciak =
$tmp[0];
}
else{
$dzieciak =
$value;
}
}
if($run ==
0)
{
if (strstr($value,
"_") !=
false)
{
$tmp =
explode("_",
$value);
$data =
$tmp[0];
$childs =
$tmp[1];
$dzieciak =
$data;
$data =
$root_node;
}
else
{
$dzieciak =
$value;
$data =
$root_node;
}
}
$rootMenu = Doctrine_Core::
getTable(‘Model_App_Menus’)->
findOneById($data);
$childMenu = Doctrine_Core::
getTable(‘Model_App_Menus’)->
findOneById($dzieciak);
$childMenu->
getNode()->
moveAsLastChildOf($rootMenu);
}
echo "Done";
die();
}
Wsio - działa, sortowanie Ajax’em i wyświetlanie - ogólnie problem drzewa załatwiony w 15 minut :)
Jeżeli jakieś informacje okazały się przydatne to zapraszam na wspólną kawę, chętnie podyskutuję.
PHP, develop