PDA

View Full Version : ساختار و روش ذخیره سازی و نمایش کامنت ها و دسته های تو در تو در php و mysql



soheilyou
سه شنبه 25 فروردین 1394, 20:20 عصر
سلام دوستان شاید این سوال خیلی کلی باشه ! ولی در نگاهی عمیق تر میتونه یه دیدِ بسیار مناسب برای یه اصل مهم(به نظر من البته ) بهم بده !

مشکل اصلی من نمایشِ کامنت ها یا دسته های تو در توعه (یعنی مثلا کامنتی که جواب یه کامنت دیگه س یا دسته ای که زیر دسته ی یه دسته ی دیگه ست ) مثلا توی ورد پرس شما میتونید برای هر دسته ای یه دسته ی مادر انتخاب کنید و موقع نمایش اون ها رو به صورت تو در تو نمایش میده !

حال من به عنوان مثال کامنت ها رو شروع میکنم
من در پروژه ی خودم table ای شبیه به این table رو برای ذخیره سازی کامنت ها در نظر گرفتم :

130338

parent_id ینی اینکه این کامن در پاسخ به کامنتی با فلان آیدی است ! (parent_id = 0 یعنی که کامنت مستقله و مادر نداره )
حالا فرض کنید من میخوام تمام کامنت ها رو نشون بدم و اگر کامنتی پاسخی داشت ، پاسخش به صورت فرو رفته و پایینش نشون داده بشه و به همین ترتیب اگر پاسخشم ، پاسخ داشته دوباره یه فرو رفتگی دیگه داشته باشه و .... مثه این شکل :

130339

حالا برای این کار من یه select میزنم و کل جدول رو میگیرم و تو آرایه ی
$database ذخیره میکنم : برای مثال وقتی اونو print_r میکنم این خروجی رو میده :




Array
(
[0] => Array
(
[id] => 1
[content] => co1
[author_id] => 2
[date] => 1
[parent_id] => 0
)

[1] => Array
(
[id] => 2
[content] => co2
[author_id] => 2
[date] => 2
[parent_id] => 0
)

[2] => Array
(
[id] => 3
[content] => co3
[author_id] => 3
[date] => 3
[parent_id] => 1
)

[3] => Array
(
[id] => 4
[content] => co4
[author_id] => 4
[date] => 4
[parent_id] => 0
)

[4] => Array
(
[id] => 5
[content] => co5
[author_id] => 5
[date] => 5
[parent_id] => 3
)

[5] => Array
(
[id] => 6
[content] => co6
[author_id] => 6
[date] => 6
[parent_id] => 1
)

[6] => Array
(
[id] => 7
[content] => co7
[author_id] => 7
[date] => 7
[parent_id] => 3
)

[7] => Array
(
[id] => 8
[content] => co8
[author_id] => 8
[date] => 8
[parent_id] => 5
)

)


خوب دوستن برای نمایش تو در توعه اینها این فکر به ذهنم رسید که یه حلقه ی foreach میزنم و چک میکنم اگه parent_id ش 0 صفر نبود بره چک کنه و ..... ولی خوب ایده ش آسون بود ولی تو اجراش گیر کردم !
این کد رو نوشتم ولی ارور میده و خودمم میدونم مشکل داره ولی چون آرایه ها هی تو در تو میشن گیج میشم !

این کد منه که اشتباهه :






<?php/**comments*/class Comment extends CI_Controller{ function index() { $this->load->model('comment_model'); $database = $this->comment_model->All() ; echo "<pre>"; print_r($database); echo "</pre>"; $this_main = array() ; $first_total = array() ; foreach ($database as $key => $main_v) { $id = $main_v['id'] ; $this_main[$id] = array() ;
$this_main[$id]['id'] = $main_v['id'] ; $this_main[$id]['content'] = $main_v['content'] ; $this_main[$id]['author_id'] = $main_v['author_id'] ; $this_main[$id]['date'] = $main_v['date'] ; $this_main[$id]['parent_id'] = $main_v['parent_id'] ; $this_main[$id]['type'] = $main_v['type'] ; $this_main[$id]['childs'] = array();
$first_id = $this_main[$id]['id'] ; $first_parent = $this_main[$id]['parent_id'] ; $first_childs = array() ; $first_total = $this_main[$id] ;


if ($first_parent != 0) { $second_childs = array() ; $second_parent = $this_main[$first_parent]['parent_id'] ; //----- if ($second_parent != 0) { $third_childs = array() ; $third_parent = $this_main[$second_parent]['parent_id'] ; //----- if ($third_parent != 0) { $fourth_childs = array() ; $fourth_parent = $this_main[ $third_parent]['parent_id'] ; //----- if ($fourth_parent != 0) { $fifth_parent = $this_main[ $fourth_parent]['parent_id'] ; array_push($fourth_childs,$this_main[$id]) ; } else { array_push($third_childs,$this_main[$id]) ; } } else { array_push($second_childs,$this_main[$id]) ; } } else { array_push($first_childs,$second_childs) ; } } array_push($this_main[$id]['childs'] ,$first_childs) ;
echo "<hr><pre>"; print_r($this_main[$id]);
}

}}?>






میشه دوستان با تجربه یه کد روش تر و تمیز بهم بگید که خودتونم ازش جواب گرفته باشد ! لطف راهنمایی کلی نفرمایید چون توش گیر میکنم متاسفانه !
اگرم یه کد بنویسید که کانسپت کار رو بگیرم عالی میشه !
ممنونم

-سیّد-
جمعه 28 فروردین 1394, 00:52 صبح
در واقع شما به یه ساختار درختی نیاز دارید. احتمالاً اگه یه جستجو توی وب بکنید، کلاس‌هایی برای کار با درخت‌ها (Tree) پیدا می‌کنید. اما اینجا سعی می‌کنیم به جای ماهی دادن، ماهی‌گیری یاد بدیم!

در ضمن برای این که بتونید کامل کارتون رو انجام بدید، باید کار به صورت بازگشتی انجام بشه.

به نظرم می‌رسه برای این کار، اول یه طراحی ساده بکنیم:
این قیافه‌ی نهایی‌ای هست که قراره از این دیتای مثال شما در بیاد:


1
--> 3
------> 5
----------> 8
------> 7
--> 6
2
4

اینجا فقط ID ها رو نوشتم، که مشخصه متناظر با هر ID یه دیتایی هم هست.
خوب یه نگاه به نقاشی‌ای که کشیدم بندازیم: اگه ستونی نگاهش کنیم، توی سطح اول کامنت‌های ۱، ۲ و ۴ قرار دارن. حالا هر کدوم از کامنت‌ها، یه دیتا دارن، و یه فهرست از بچه‌ها (که ممکنه این فهرست خالی باشه). پس یه کلاس داریم به نام Tree، که توش یه تعداد Node هست. هر Node یه دیتا داره، و یه فهرست از Node های دیگه.

کلاس Node به این شکل می‌شه:


class Node {
private $data;
private $children = array();

public function __construct($data) {
$this->data = $data;
}

public function addChild(Node $child) {
$this->children[] = $child;
}

public function print() {
...
}
}

یه تابع constructor داریم که دیتایی که از پایگاه داده اومده رو ورودی می‌گیره، و یه تابع addChild که یه Node دیگه رو به عنوان بچه‌ی این Node اضافه می‌کنه.
تابع print اول اطلاعات این کامنت رو می‌نویسه، بعد روی بچه‌هاش حلقه می‌زنه و اطلاعات اونا رو به صورت بازگشتی می‌نویسه:


public function print() {
echo $this->data; // اینجا رو دیگه خودتون درست کنید که کامنت به چه صورت قراره چاپ بشه
foreach($this->children as $child) {
$child->print();
}
}


اگر هم قراره کامنت‌های تو در تو داخل تگ خاصی قرار بگیرن، می‌تونید در ابتدا و انتهای همین تابع تگ مورد نظر رو بذارید. مثلاً اگه قراره هر کامنت توی یه تگ div باشه:


public function print() {
echo '<div>';
echo $this->data; // اینجا رو دیگه خودتون درست کنید که کامنت به چه صورت قراره چاپ بشه
foreach($this->children as $child) {
$child->print();
}
echo '</div>';
}


بعدش می‌ریم سراغ کلاس Tree:

class Tree {
داخل این کلاس، شما لازم دارید وقتی دارید حرکت می‌کنید روی کامنت‌ها، بتونید با یه lookup بفهمید که باباش کجاس. برای همین از یه آرایه استفاده می‌کنیم که کلیدش ID کامنت باشه و مقدارش Node متناظرش:


private $index = array();

یه دونه آرایه‌ی دیگه هم لازم داریم که بچه‌های سطح یک درخت رو نشون می‌ده: (البته می‌شد با یک آرایه هم کار رو تموم کرد، ولی کد پیچیده‌تر می‌شد)


private $children = array();

یه تابع add می‌نویسیم که از طریقش بشه کامنت‌ها رو توی درخت اضافه کرد:


public function add($c) {

توی این تابع اول Node مورد نظر رو می‌سازیم:


$node = new Node($c);

بعد Node ساخته شده رو توی آرایه‌ی index اضافه می‌کنیم که بعداً بشه راحت پیداش کرد:


$this->index[$c['id']] = $node;


بعد نگاه می‌کنیم ببینیم ننه داره یا نه! :)
اگه ننه نداره، باید توی سطح اول اضافه بشه، و اگه داره، باید بره به عنوان بچه‌ی ننه‌اش(!!) قرار بگیره:


if ($c['parent_id'] == 0) {
$this->children[] = $node;
}
else {
$parent = $this->index[$c['parent_id']];
$parent->add($node);
}
}

توجه کنید که اینجا فرض بر این بوده که کامنت‌ها به ترتیب id به تابع add داده می‌شن. یعنی اونجا که داریم داخل متغیر index می‌گردیم و ننه‌اش رو پیدا می‌کنیم، فرض می‌کنیم که حتماً موجود هست. بنابراین حواستون باشه اونجا که دارید داده‌ها رو از توی پایگاه داده load می‌کنید، باید به ترتیب id باشن.

حالا یه تابع اضافه می‌کنیم که کل این درخت رو برامون echo کنه. برای این کار، روی تمام بچه‌های سطح یک حرکت می‌کنیم و اونا رو توسط تابع print خودشون چاپ می‌کنیم:


public function print() {
foreach($this->children as $node) {
$node->print();
}
}


حالا یه شیء از روی این کلاسمون می‌سازیم، و بعد روی کامنت‌ها حرکت می‌کنیم و دونه دونه توی کلاس اضافه‌شون می‌کنیم:


$tree = new Tree();
foreach ($database as $v) {
$tree->add($v);
}

و در نهایت کل درخت رو چاپ می‌کنیم:

$tree->print();

من کل کد رو توی ویرایشگر داخل سایت زدم، برای همین ممکنه اشکال نحوی و یا باگ داشته باشه.
لطفاً اگه به نتیجه نرسیدید بگید دقیق‌تر بررسی کنم.

plague
یک شنبه 30 فروردین 1394, 07:04 صبح
شما یه فیلد دیگه هم تو تیبلت نیاز داری که نشون بده این مطلب برای کدوم پست یا موضوع هستش که نزاشتی بگزریم
اگه از CI استفاده میکنی توی helper یه تابع بزاربرای نمایششون .... راجب توابع برگشتی هم یکم تحقیق کن




function comments( $parent_id = 0 ){

$comments = $this->db->query( " select * from table where parent_id = $parent_id );

if($comments->num_rows() == 0 ) return ;

foreach($comments->result() as $c )
{
echo '<div class="comment">';
echo $c->author_id.' : '. $c->content;
comments($c->parent_id);
echo '</div>';
}


}

-سیّد-
یک شنبه 30 فروردین 1394, 09:46 صبح
شما یه فیلد دیگه هم تو تیبلت نیاز داری که نشون بده این مطلب برای کدوم پست یا موضوع هستش که نزاشتی بگزریم
اگه از CI استفاده میکنی توی helper یه تابع بزاربرای نمایششون .... راجب توابع برگشتی هم یکم تحقیق کن




function comments( $parent_id = 0 ){

$comments = $this->db->query( " select * from table where parent_id = $parent_id );

if($comments->num_rows() == 0 ) return ;

foreach($comments->result() as $c )
{
echo '<div class="comment">';
echo $c->author_id.' : '. $c->content;
comments($c->parent_id);
echo '</div>';
}


}

بله اینطوری هم می‌شه. فقط اشکالش اینه که توی هر بار فراخوانی تابع بازگشتی شما (که می‌شه به تعداد تمام کامنت‌های پست مورد نظر)، یه query دیتابیس وجود داره، که این می‌تونه سیستم شما رو به نابودی تدریجی نزدیک کنه! :لبخندساده:

plague
یک شنبه 30 فروردین 1394, 19:40 عصر
بله اینطوری هم می‌شه. فقط اشکالش اینه که توی هر بار فراخوانی تابع بازگشتی شما (که می‌شه به تعداد تمام کامنت‌های پست مورد نظر)، یه query دیتابیس وجود داره، که این می‌تونه سیستم شما رو به نابودی تدریجی نزدیک کنه! :لبخندساده:

سلام
هدف نشون دادن نحوه کار تابع بازگشتی بود نه نوشتن کد آماده بهینه شده
اون قسمتش هم کار سختی نیست با استفاده از آرایه بجای دیتبایس میشه مشکل رو حل کرد یچیزی شبیه کد شما

این کد رو همینجا نوشتم ممکنه اررور سینتکس داشته باشه



class comments {
public $dtabase = array();

public function __construct(){
$this->load_db();
}

//// بارگزاری دیتبایس در یک آرایه
function load_db(){
$query = $this->db->("select * from comments");
foreach($query->result() as $q )
{
$this->database[] = array('author_id'=>$q->author_id , 'content'=> $q->content , 'parent_id'=>$q->parent_id);
}
}


// خواندن داده ها از آرایه دیتبایس
function read_db( $parent_id = 0 ){
$result = array();
foreach($this->database as $db)
{
if($db['parent_id'] == $parent_id )
$result[] = $db;
}
return $result ;
}


//// تابع اصلی
function comments( $parent_id = 0 ){

$comments = $this->read_db($parent_id);

if( empty($comments) ) return ;

foreach($comments as $c )
{
echo '<div class="comment">';
echo $c['author_id'] .' : '. $c['content'];
$this->comments($c['parent_id']);
echo '</div>';
}


}

}

-سیّد-
یک شنبه 30 فروردین 1394, 21:28 عصر
// خواندن داده ها از آرایه دیتبایس
function read_db( $parent_id = 0 ){
$result = array();
foreach($this->database as $db)
{
if($db['parent_id'] == $parent_id )
$result[] = $db;
}
return $result ;
}

می‌دونید تفاوت اصلی کد بنده با کد شما توی این بخشه. شما توی این بخش، به ازای هر بار فراخوانی این تابع (که به ازای هر کامنت یک پست یک بار فراخوانی می‌شه)، یه دور روی کل آرایه حرکت می‌کنید و کامنت‌های مورد نظر رو بیرون میارید. البته که این کار هزینه‌اش خیلی خیلی کمتر از کد قبلی هست که با پایگاه داده کار می‌کرد (چون اینجا کلش توی RAM هست)، ولی باز هم هزینه‌اش به مراتب بیشتر از هزینه‌ی یه lookup ساده توی آرایه هست. یعنی در واقع شما توی یک فاز کار رو تموم می‌کنید، که البته هزینه‌اش زیاده (که اگه تعداد کامنت‌ها رو n فرض کنیم، می‌شه 2^n). کد بنده توی ۲ فاز کار می‌کرد، ولی هر فاز فقط یه بار حرکت روی کل داده‌ها می‌کرد (که می‌شه 2*n).

البته تفاوت اینها توی یه سایت خیلی کوچیک هیچی نیست، ولی سایت که بزرگ می‌شه و تعداد کامنت‌ها بالا می‌ره، و همچنین تعداد کاربرای سایت بالا می‌ره و در نتیجه تعداد درخواست‌ها بالا می‌ره، کم کم این چیزا اهمیت پیدا می‌کنن.

n0o0b_sina
دوشنبه 31 فروردین 1394, 12:17 عصر
سید جان شما چطوری id دیدگاه هارو توی ایندکس آرایه قراره بزارید؟! آیا با حلقه این کارو میکنید؟! اگه آره که همون روش دوستمون میشه! یا اگه روش دیگ ای هست بفرمایید.

-سیّد-
دوشنبه 31 فروردین 1394, 13:47 عصر
سید جان شما چطوری id دیدگاه هارو توی ایندکس آرایه قراره بزارید؟! آیا با حلقه این کارو میکنید؟! اگه آره که همون روش دوستمون میشه! یا اگه روش دیگ ای هست بفرمایید.
کدش رو که بالاتر زدم.
بله با حلقه این کار رو می‌کنم. در فاز اول، یه دور روی تمام کامنت‌ها حرکت می‌کنم و اونا رو توی ساختار داده‌ای مورد نظر (درخت) می‌ریزم (توی تابع add). همینجا دارم توی آرایه هم نگهشون می‌دارم:

$this->index[$c['id']] = $node;

و در ضمن، همینجا نگاه می‌کنم ببینم اگه لازمه زیر کامنت دیگه‌ای قرارش می‌دم:


if ($c['parent_id'] == 0) {
$this->children[] = $node;
}
else {
$parent = $this->index[$c['parent_id']];
$parent->add($node);
}

این شد یه دور حرکت روی کامنت‌ها (n تا حرکت)، و هیچ کار بازگشتی‌ای هم انجام نشده.
در فاز دوم، روی نتیجه حرکت می‌کنم و چاپش می‌کنم (توی تابع print):


public function print() {
foreach($this->children as $node) {
$node->print();
}
}

این هم می‌شه دور دوم (این هم n حرکت). اینجا به صورت بازگشتی روی node ها حرکت می‌کنیم، ولی دیگه خبری از lookup کردن و گشتن و اینها نیست. فقط حرکت می‌کنیم و چاپ می‌کنیم.
جمعش شد 2*n.

توی روشی که دوستمون گفتن، توی هر بار فراخوانی تابع comments، یه دور تابع read_db فراخوانی می‌شه:

$comments = $this->read_db($parent_id);
که توی این تابع، یه دور روی کل کامنت‌ها (n) حرکت می‌کنیم و اونایی که parent_id شون برابر اونی که می‌خوایم هست، استخراج می‌کنیم:


// خواندن داده ها از آرایه دیتبایس
function read_db( $parent_id = 0 ){
$result = array();
foreach($this->database as $db)
{
if($db['parent_id'] == $parent_id )
$result[] = $db;
}
return $result ;
}

پس هر بار هزینه‌ی فراخوانی این تابع n هست.
و در نهایت به صورت بازگشتی، تمام کامنت‌ها رو با تابع comments چاپ می‌کنیم، که تعدادشون هست n. خوب با توجه به این که توی هر بار فراخوانی تابع comments داریم تابع read_db رو صدا می‌زنیم، که هزینه‌ی n داره، و خود comments هم n بار فراخوانی می‌شه، کل هزینه می‌شه 2^n.

n0o0b_sina
دوشنبه 31 فروردین 1394, 16:10 عصر
من کم دقتی کردم حق با شماست، شما 1 دور روی کامنت ها حرکت میکنید دوستمون (plague (http://barnamenevis.org/member.php?123420-plague)) هر بار که میخواد بره چک کنه توی کامنت ها حرکت میکنه.
فقط یه سوالم من راجبه مهندسی نرم افزار ازتون دارم، به نظرتون تاپیک بزنم خوبه یا پ.خ میتونید جواب بدید؟

-سیّد-
دوشنبه 31 فروردین 1394, 18:45 عصر
من کم دقتی کردم حق با شماست، شما 1 دور روی کامنت ها حرکت میکنید دوستمون (plague (http://barnamenevis.org/member.php?123420-plague)) هر بار که میخواد بره چک کنه توی کامنت ها حرکت میکنه.
فقط یه سوالم من راجبه مهندسی نرم افزار ازتون دارم، به نظرتون تاپیک بزنم خوبه یا پ.خ میتونید جواب بدید؟
خوب فکر می‌کنم یه تاپیک جدید اگه بزنید می‌تونه برای همه‌ی افراد مفیدتر باشه. البته اگه بتونم جواب بدم! اگه نتونم هم حتماً افراد حرفه‌ای‌تری اینجا هستن که بتونن جواب بدن.