Добре работи от бекенда към предния край...
Можете да извикате една нерекурсивна съхранена процедура (sproc) от вашия php скрипт, който генерира йерархията на съобщенията за вас. Предимството на този подход е, че трябва само да направите ЕДИНИЧНА извикване от php към вашата база данни, докато ако използвате вграден SQL, тогава ще извършвате толкова обаждания, колкото има нива (най-малко). Друго предимство е, че тъй като е нерекурсивен sproc, той е изключително ефективен и също така поддържа вашия php код добър и чист. И накрая, и трябва да кажа това за протокола, че извикването на съхранени процедури е по-сигурно и по-ефективно от всеки друг метод, защото трябва само да ПРЕДОСТАВЯТЕ разрешения за изпълнение на потребителя на вашето приложение и съхранените процедури изискват по-малко двупосочни пътувания до базата данни от всички други методи, включително параметризирани заявки, които изискват поне 2 извиквания за една заявка (1 за настройка на шаблона на заявката в db, другият за попълване на параметрите)
Ето как ще извикате съхранената процедура от командния ред на MySQL.
call message_hier(1);
и ето набора от резултати, който създава.
msg_id emp_msg parent_msg_id parent_msg depth
====== ======= ============= ========== =====
1 msg 1 NULL NULL 0
2 msg 1-1 1 msg 1 1
3 msg 1-2 1 msg 1 1
4 msg 1-2-1 3 msg 1-2 2
5 msg 1-2-2 3 msg 1-2 2
6 msg 1-2-2-1 5 msg 1-2-2 3
7 msg 1-2-2-1-1 6 msg 1-2-2-1 4
8 msg 1-2-2-1-2 6 msg 1-2-2-1 4
Добре, така че сега имаме възможността да извлечем пълно или частично дърво на съобщенията, като просто извикаме нашия sproc с всеки начален възел, който ни е необходим, но какво ще правим с набора от резултати??
Е, в този пример реших, че ще генерираме XML DOM с него, след което всичко, което трябва да направя, е да трансформирам (XSLT) XML и ще имаме вложена уеб страница за съобщения.
PHP скрипт
PHP скриптът е доста прост, той просто се свързва с базата данни, извиква sproc и зацикля набора от резултати, за да изгради XML DOM. Не забравяйте, че се обаждаме в db само веднъж.
<?php
// i am using the resultset to build an XML DOM but you can do whatever you like with it !
header("Content-type: text/xml");
$conn = new mysqli("localhost", "foo_dbo", "pass", "foo_db", 3306);
// one non-recursive db call to get the message tree !
$result = $conn->query(sprintf("call message_hier(%d)", 1));
$xml = new DomDocument;
$xpath = new DOMXpath($xml);
$msgs = $xml->createElement("messages");
$xml->appendChild($msgs);
// loop and build the DOM
while($row = $result->fetch_assoc()){
$msg = $xml->createElement("message");
foreach($row as $col => $val) $msg->setAttribute($col, $val);
if(is_null($row["parent_msg_id"])){
$msgs->appendChild($msg);
}
else{
$qry = sprintf("//*[@msg_id = '%d']", $row["parent_msg_id"]);
$parent = $xpath->query($qry)->item(0);
if(!is_null($parent)) $parent->appendChild($msg);
}
}
$result->close();
$conn->close();
echo $xml->saveXML();
?>
XML изход
Това е XML, който php скриптът генерира. Ако запазите този XML във файл и го отворите в браузъра си, ще можете да разгънете и свиете нивата.
<messages>
<message msg_id="1" emp_msg="msg 1" parent_msg_id="" parent_msg="" depth="0">
<message msg_id="2" emp_msg="msg 1-1" parent_msg_id="1" parent_msg="msg 1" depth="1"/>
<message msg_id="3" emp_msg="msg 1-2" parent_msg_id="1" parent_msg="msg 1" depth="1">
<message msg_id="4" emp_msg="msg 1-2-1" parent_msg_id="3" parent_msg="msg 1-2" depth="2"/>
<message msg_id="5" emp_msg="msg 1-2-2" parent_msg_id="3" parent_msg="msg 1-2" depth="2">
<message msg_id="6" emp_msg="msg 1-2-2-1" parent_msg_id="5" parent_msg="msg 1-2-2" depth="3">
<message msg_id="7" emp_msg="msg 1-2-2-1-1" parent_msg_id="6" parent_msg="msg 1-2-2-1" depth="4"/>
<message msg_id="8" emp_msg="msg 1-2-2-1-2" parent_msg_id="6" parent_msg="msg 1-2-2-1" depth="4"/>
</message>
</message>
</message>
</message>
</messages>
Сега можете да се откажете от изграждането на XML DOM и да използвате XSL за изобразяване на уеб страница, ако желаете, и може би просто завъртете набора от резултати и изобразете директно съобщенията. Просто избрах този метод, за да направя моя пример възможно най-изчерпателен и информативен.
MySQL скрипт
Това е пълен скрипт, включващ таблици, процесори и тестови данни.
drop table if exists messages;
create table messages
(
msg_id smallint unsigned not null auto_increment primary key,
msg varchar(255) not null,
parent_msg_id smallint unsigned null,
key (parent_msg_id)
)
engine = innodb;
insert into messages (msg, parent_msg_id) values
('msg 1',null),
('msg 1-1',1),
('msg 1-2',1),
('msg 1-2-1',3),
('msg 1-2-2',3),
('msg 1-2-2-1',5),
('msg 1-2-2-1-1',6),
('msg 1-2-2-1-2',6);
drop procedure if exists message_hier;
delimiter #
create procedure message_hier
(
in p_msg_id smallint unsigned
)
begin
declare v_done tinyint unsigned default(0);
declare v_dpth smallint unsigned default(0);
create temporary table hier(
parent_msg_id smallint unsigned,
msg_id smallint unsigned,
depth smallint unsigned
)engine = memory;
insert into hier select parent_msg_id, msg_id, v_dpth from messages where msg_id = p_msg_id;
/* http://dev.mysql.com/doc/refman/5.0/en/temporary-table-problems.html */
create temporary table tmp engine=memory select * from hier;
while not v_done do
if exists( select 1 from messages e inner join hier on e.parent_msg_id = hier.msg_id and hier.depth = v_dpth) then
insert into hier select e.parent_msg_id, e.msg_id, v_dpth + 1
from messages e inner join tmp on e.parent_msg_id = tmp.msg_id and tmp.depth = v_dpth;
set v_dpth = v_dpth + 1;
truncate table tmp;
insert into tmp select * from hier where depth = v_dpth;
else
set v_done = 1;
end if;
end while;
select
m.msg_id,
m.msg as emp_msg,
p.msg_id as parent_msg_id,
p.msg as parent_msg,
hier.depth
from
hier
inner join messages m on hier.msg_id = m.msg_id
left outer join messages p on hier.parent_msg_id = p.msg_id;
drop temporary table if exists hier;
drop temporary table if exists tmp;
end #
delimiter ;
-- call this sproc from your php
call message_hier(1);
Пълният източник за този отговор може да се намери тук:http://pastie.org/1336407 . Както вече сте отбелязали, пропуснах XSLT, но вероятно няма да преминете по XML маршрута и ако го направите, в мрежата има купища примери.
Надявам се да намерите това за полезно :)
РЕДАКТИРАНЕ:
Добавени са малко повече данни, така че да имате повече от едно основно съобщение (msg_ids 1,9,14).
truncate table messages;
insert into messages (msg, parent_msg_id) values
('msg 1',null), -- msg_id = 1
('msg 1-1',1),
('msg 1-2',1),
('msg 1-2-1',3),
('msg 1-2-2',3),
('msg 1-2-2-1',5),
('msg 1-2-2-1-1',6),
('msg 1-2-2-1-2',6),
('msg 2',null), -- msg_id = 9
('msg 2-1',9),
('msg 2-2',9),
('msg 2-3',9),
('msg 2-3-1',12),
('msg 3',null); -- msg_id = 14
Сега, ако искате просто да получите съобщенията, които са специфични за главен възел (начално съобщение), можете да извикате оригиналната съхранена процедура, преминаваща в началния msg_id на основния, който ви е необходим. Използвайки новите данни по-горе, това ще бъде msg_ids 1,9,14.
call message_hier(1); -- returns all messages belonging to msg_id = 1
call message_hier(9); -- returns all messages belonging to msg_id = 9
call message_hier(14); -- returns all messages belonging to msg_id = 14
можете да подадете всеки msg_id, който искате, така че ако искам всички съобщения под msg 1-2-2-1, тогава ще предадете msg_id =6:
call message_hier(6); -- returns all messages belonging to msg_id = 6
Въпреки това, ако искате всички съобщения за всички roots, тогава можете да извикате този нов sproc, който създадох, както следва:
call message_hier_all(); -- returns all messages for all roots.
Основният проблем с това е, че с нарастването на таблицата ви със съобщения тя ще връща много данни, поради което се фокусирах върху по-специфичен sproc, който извлича съобщения само за даден главен възел или стартиращ msg_id.
Няма да публикувам новия sproc код, тъй като е почти същият като оригиналния, но можете да намерите всички изменения тук:http://pastie.org/1339618
Последната промяна, която ще трябва да направите, е в php скрипта, който сега ще извика новия sproc, както следва:
//$result = $conn->query(sprintf("call message_hier(%d)", 1)); // recommended call
$result = $conn->query("call message_hier_all()"); // new sproc call
Надявам се това да помогне :)
call message_hier_all();