Есть несколько алгоритмов хранения деревьев в mysql, и мне кажется, что это в корне неправильно, так как реляционная база данных не предназначена для этого, а представление линейных структур данных в древовидную — один большой костыль в коде и понижение надёжности и скорости приложения. Как я задумался об использование NoSQL в частности mongoDB? Всё просто, меня на собеседование спросили:
Как построить дерево?

Я ответил:
Берём mongodb и документ с деревом из неё

Мне так и не перезвонили, но я не расстраиваюсь за то теперь я изучаю mongoDB и не люблю Mysql(к счастью или к сожалению я не знаю). В общем, разберём как всё это работает? На примере дерева комментариев написанном на php.

Сразу приведу код:

    // метод для подключения к mongodb находиться в абстрактном классе 
    // привожу его для понимания кода ниже
    /**
     * @return \MongoDB\Driver\Manager
     * */
    static function getConnect() {
        if(!is_null(self::$_connect)) {
            return self::$_connect;
        }
        self::$_connect = new \MongoDB\Driver\Manager(Config_Db::getConf()['mongodb']['connect']);
        return self::$_connect;
    }

    // наш контроллер
    
    /**
     * add comment action
     */
    public function addCommentAction()
    {
        $time = time();
        // массив с данными комментария
        //insert new comment to the page
        $arrData =  array(
            'page' => $_POST['page_id'], // id страницы в mongo
            'time' => $time, // время написания комментария
            'name' => $_POST['name'], // имя написавшего
            'comment' => $_POST['comment'] // сам комментрарий
        );

        $connect = Core_Model_Mongo::getConnect(); // подключение к монгодб

        // две строки ниже подготовка к записи так как они не относятся к алгоритму 
        // то в дальнейшем я не буду на них заострять внимание
        $write = new MongoDB\Driver\BulkWrite(); 
        $writeConcern = new MongoDB\Driver\WriteConcern(MongoDB\Driver\WriteConcern::MAJORITY); 

        // и так, если человек отвечает на чей-то комментарий, то к нам приходит reply с id комментария
         if(isset($_POST['reply']) && !empty($_POST['reply'])) {
            // id комментария       
            $reply = $_POST['reply'];
            // путь для сохранения комментария
            // другими словами, это путь по которому будет сохранен комментарий
            $path = '';
            if(isset($_POST['path']) && !empty($_POST['path'])) {
                $path = $_POST['path'];
            } else {
                $path = 'replies';
            }

            // собственно, обновление данных по комментарию
            $write->update(
                array('_id' => new MongoDB\BSON\ObjectID($reply)), // загружаем комментарий 
                array('$push' => array($path => $arrData) ) // делаем push, в существующие данные, относительно path
            );

        } else {
            $write->insert($arrData); // если комментарий новый вставляем в базу
        }
        
        // запускаем запрос
        $connect->executeBulkWrite(Config_Db::getConf()['mongodb']['db'] . '.comments', $write, $writeConcern);
        
        // и возвращаемся обратно 
        header('Location:' . $_POST['back_url']);
    }
   

И так в переменную path передаётся путь для сохранения, к примеру у нас в монго есть комментарий, представим его в виде 'чистого' массива, так как в реальности это массив объектов мы упростим всё до массива.

Массив с одним коментарием
   array (
       'name' => 'test',
       'comment' => 'test comment',
   );


После update, с push и переменной path = 'reply', наш массив приобретёт вид:

Массив после push
   array (
       'name' => 'test',
       'comment' => 'test comment',
       'reply' => array (
              [0] => 
                   array( 
                       'name' => 'test reply test'
                       'comment' => 'test comment reply'
                   )
       )
   );


А если отвечать на вложенный комментарий, то путь приобретёт вид 'reply.0.reply', то есть в reply комментария взять 0 элемент, и вставить туда новое поле reply с данными, после операции мы получим следующий массив.

Массив с репликой на вложеный комментарий
   array (
       'name' => 'test',
       'comment' => 'test comment',
       'reply' => array (
              [0] => 
                   array( 
                       'name' => 'test reply test'
                       'comment' => 'test comment reply'
                       'reply' => array (
                                  [0] => 
                                        array( 
                                             'name' => 'test reply test'
                                             'comment' => 'test comment reply'
                                        )
                                   )
                             )
                        )
                  );


И это всё, весь алгоритм, просто вставить и потом просто взять этот массив для рендера таким какой он был, есть и будет. Рендер не буду расписывать так как рендер это не такая важная часть, да и я просто его делаю через print_r() шутка конечно, но принцип тот же… Спасибо за внимание дорогие хаброюзеры.

П.С.: В примере представлен алгоритм и в нём есть допущения, а именно отсутствие сортировки комментариев и её персистетности.

Комментарии (54)