Как стать автором
Обновить

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

$arrData =  array(
            'page' => $_POST['page_id'], // id страницы в mongo
            'time' => $time, // время написания комментария
            'name' => $_POST['name'], // имя написавшего
            'comment' => $_POST['comment'] // сам комментрарий
        );

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

А еще нефильтрованные данные из $_POST, и header прямо в коде.
Это же не принципиально, во первых, если расписывать всё до мелочи алгоритм затеряеться в проверках и будет несовсем ясно человеку суть. А $_POST фильтрую при рендере, и не вижу в этом ничего страшного.
Так же в какой то момент столкнетесь с тем что вы больше ничего в документ не можете добавить. Т.к. есть лимит на размер документа. Не есть конечно и GridFS. Мне кажется вам правильно не перезвонили.
Так то храниться отдельно комент и е го потомки в одном документе. т.е. получаеться при выборке коллекция комментариев, в которых есть дочерние элементы.
Погодите я что то не понял. А где у вас потомки хранятся?
По сути есть коментарий_родитель-> (коментарий_потомок, коментарий_потомок) я привёл пример в посте.
Ну тогда вы лукавите… Или в посте или в комментарии. В посте у вас вся ветка внутри одного документа и в определённый момент у вас новый коммент просто не поместится в документ. А в комментарии вы уже пишете что потомки не в том же документе что и коммент 0 уровня. Определитесь уж.
да нет не вся ветка, я в посте не привёл все данные 'сопутствующие' что бы легче читать было, в посте приходит id страницы. Если нет в посте reply производиться insert в монго т.е. комментарий первого уровня, а вот уже реплики на комментарий привязываеться к родителю(т.е. тогда когда есть reply ), по коду к стати довольно хорошо это видно, видимо плохо видно, видимо мой касяк.
Да нет я всё прекрасно понял. У вас документ в коллекции comments выглядит вот так http://joxi.ru/DrlDO6h4edbqrP. То есть у вас допустим к статье есть скажем 5 корневых комментариев у вас в этой коллекции будет 5 документов, и внутри этих документов будет вся ветка. И в какой то момент вы упрётесь в объём документа! Особенно с учётом того что вы не просто сам коммент там хранить будете. Вы в какой то момент захотите хранить всевозможную мета информацию.
а зачем фильтровать данные при записи? Их фильтровать надо при отдаче, если они иcпользуются для построения верстки. В SPA которые сами строят на клиенте и строят строго через DOM — вообще ничего фильтровать не надо (ок, надо ескейпить кавычки если отдаем json-ом)
Фильтровать при записи нужно как раз для того, чтобы не тратить каждый раз ресурсы на фильтрацию при отдаче. А ещё это в какой-то мере поможет уберечься от NoSQL-иъекций.
В большинстве случаев, с которыми я встречался — подобная логика граничила с ересью не применялась исходя из следующих соображений — логика фильтрации зависит от логики рендера — логика рендера поменялась — меняем логику фильтрации. Если мы храним модифицированные данные — попадаем на лишние телодвижения. Плюс это неправильно — модифицировать данные полученные от пользователя, они могут использоваться не только при рендере, в каждом контексте свои правила и ограничения. А если важны ресурсы — переносите рендер на клиента и все.

От инъекций так не защищаются. Для sql это хранимые процедуры + параметризованные запросы, для noSQL пример который вы привели — не СОХРАНЯЕТ пользовательские данные а использует их в запросе — немного не то что мы обсуждаем. И там нужна не фильтрация/экранизация а санитизация — чуть чуть другая процедура.
По поводу первого пункта мы можем долго спорить. Всё же я считаю, что если вы храните любые данные, пришедшие от юзера, сколько бы и какими бы они ни были, то это, мягко говоря, странно. Когда вы проектируете сервис, то с большой долей вероятности знаете чего хотите и примерно представляете, что будет дальше. Исходя из этого проектируете модель данных. Вообще у меня есть сомнения, что мы говорим об одном и том же, изначально мне показалось, что вы предлагаете сохранять любые данные от юзера (100-мегабайтный JSON, картинка с котятами и т.п.) как значение, а потом из него пытаться выбрать нужные поля.

По поводу "не сохраняет". Это не важно. Даже, если злоумышленник может получить список всех юзеров сервиса (включая их личные данные), то это уже плохо.
  • if(!is_null(self::$_connect)) {
  • if(isset(self::$_connect)) {

  • $write = new MongoDB\Driver\BulkWrite();
  • $writeConcern = new MongoDB\Driver\WriteConcern(MongoDB\Driver\WriteConcern::MAJORITY);
  • $write = new \MongoDB\Driver\BulkWrite();
  • $writeConcern = new \MongoDB\Driver\WriteConcern(MongoDB\Driver\WriteConcern::MAJORITY);

  • $path = '';
  • if(isset($_POST['path']) && !empty($_POST['path'])) {
  • $path = $_POST['path'];
  • } else {
  • $path = 'replies';
  • }
  • $path = (isset($_POST['path']) && !empty($_POST['path'])? $_POST['path']: 'replies';

  • array('_id' => new MongoDB\BSON\ObjectID($reply)), // загружаем комментарий
  • array('_id' => new \MongoDB\BSON\ObjectID($reply)), // загружаем комментарий
В одном месте он у Вас стоит, в другом нет. Шатание стиля.
Ok, спасибо, незаметил даже...
То же самое можно сделать в мускуле с помощью JSON Data type
Да и в Постгресе с ним же.
>if(isset($_POST['reply']) && !empty($_POST['reply'])) {

Я понимаю что пост учебный и все дела, но не учите других писать дурной код пожалуйста.
И, на всякий случай, чтобы это замечание стало конструктивным, я добавлю, что можно просто написать if(!empty($_POST['reply'])) — по смыслу это будет то же самое, что и «isset && !empty». Выражение «empty» полностью покроет как ситуацию с несуществующим индексом «reply», так и ситуацию, когда значение пустое или равно нулю (или чему-то, что в приведении к boolean возвращает FALSE).

Для иллюстрации, выражение if(isset($_POST['reply']) && !empty($_POST['reply'])) по своей логике выглядит так:

if(isset($_POST['reply']) && isset($_POST['reply']) && $_POST['reply'] == false) {
    // Do something
}`

Связка «isset && !empty» в качестве связки — это, примерно, как всё время повторять по два раза, или, например, по два раза повторять одно и то же — всё время одно и то же по два раза в тексте или разговоре повторять (или в тексте повторять по два раза, например).
$_POST['reply'] == false

Сорри, вместо «==» нужно было поставить «!=» — опечатался.
Мне стыдно (( замечание конструктивное.
Во-первых, это зависит от того, зачем вам это дерево нужно. Например, в случае с комментариями к статье — гораздо проще к каждому комментарию добавить айдишник статьи и уже потом сформировать дерево комментариев на приличном языке программирования.
Во-вторых, нужно учитывать что у документа есть ограничения на максимальный размер
В-третьих, нужно учитывать что атомарный апдейт документа — это блокировка работы со всем деревом внутри документа.
В-четвертых, чем не устраивают графовые бд для деревьев?
Какой по вашему мнению 'приличный язык програмиирования'? Пока я к сожалению незнал о графовых б.д. стыдно. Спасибо вам за наводку.
В монго конечно можно хранить, но не забывать про дополнительные данные, например кол-во комментариев, или вот как с такой структурой получить все комментарии пользователя?
Просто пишем все коментарии пользвателя в sql и получаем, это частные задачи, не относящиеся к алгоритму(последовательности действий).
С MongoDB в итоге всё и приходит к таким костылям.
Согласен кастыль. Но на mysql кастыль построить дерево. На монго выбрать коментарии, подсчет коментариев. По этому и выбор падает на совместном использование. MySql хранение коментариев подсчеты и т.д. монго для хранения структуры.
Но на mysql кастыль построить дерево
Дерево из однородных данных(комментарии в данном случае) в реляционных бд строится достаточно просто и без костылей.

table comments

id
parent_id


Что в дальнейшем позволяет сортировать как угодно, получать все комменты одного автора, считать количество и т.п
Не вижу в этом костылей ИМХО.
ok. Вы имеете id | parent_id | comment | name минимальный набор полей как построить дерево. Сделать рекурсию и к каждому корневому коментарию, выбирать с базы parent'ы. Ок, всё выбираеться. Но есть одно но вы сделали кучу запросов и сервер упал на этапе, просто выборки. Алгоритм выполняет много запросов и обрабатывает кучу данных, что не так оптимально. И так вы модернизируете алгоритм вводите понятия level и right_id и left_id что бы одним запросом вытянуть все коментарии, в порядке комент level1-> комент level2 ->комент level1 всё вроде бы просто…
Но вам надо сформировать массив вида comment['parent']->array(comment[]) думаю ясно логика, вы делаете перебор линейного массива и строите, древовидную структуру. Так вот вы взяли данные, выполнили операции перебора этих данных и модификации в массив. В монгоДБ мы просто сохраняем этот массив и просто берём его, тут вы взяли данные и не произвели над ними операций. Какой алгоритм лучше по вашему мнению? Да и видно что вы не строили древовидные структуры?
NestedSets в помощь! Любой уровень вложенности и любая глубина дерева выбирается одним запросом. Единственный минус. Дорогая вставка.
именно этот алгоритм я вам и описал, недочитываете до конца или через строку?
Хорошо извиняюсь. Не дочитал(разделять нужно комментарий на абзацы). В середине вы описали Nested Sets.

Однако дальше вы опять предлагаете то о чём я вам писал выше(пихать всю ветку комментариев в один документ). О том что в один прекрасный момент вы не сможете добавить очередной комментарий в ветку https://habrahabr.ru/post/279915/#comment_8815465

И да я не отрицаю для проекта с около нулевой посещаемостью и с десятком другим комментариев ваше решение подойдёт отлично.

P.S. Для продуктивной дискуссии всё же предлагаю вам для начала ознакомиться хоть немного с документацией MongoDB Limits and Thresholds

Я тоже должен извиниться, я только начинаю изучать mongoDB, и нахожу всё больше и больше плюсов по сравнению с sql, может это просто первое впечатление. Ну да ладно, оффтоп, спс за ссылку.
Хорошо, предлагаю решение проблемы дабы избежать рекурсивность.
Как правило в случае с комментариями необходимо иметь возможность получать полное дерево от корневого комментария или от дочернего зная его id.
проблему решить достаточно просто, вот пример таблицы:
id | parent_id | root_id | comment | name ...
В данном случае parent_id это всего лишь указатель структуры дерева не играющий роль при выборке полного дерева.
А root_id указатель на корневой комментарий позволяющий выбрать все дерево одним запросом.
Данный подход так же избавит от минуса NestedSets.
Пример запроса привести сможете?
//По id корня
SELECT
id, parent_id, root_id, comment
FROM comments
WHERE root_id = {id} OR id = {id}
//По id дочернего элемента
SELECT
id, parent_id, root_id, comment
FROM comments
WHERE root_id = (SELECT root_id FROM comments WHERE id = {id}) OR id = (SELECT root_id FROM comments WHERE id = {id})
Если пугаетесь вложенных запросов во втором случае, то можно оформить в виде процедурки с 2 запросами.
Хорошо, и как вы собираетесь без рекурсии обойтись, вам тот же массив надо будет перебирать по несколько раз выискивая parent_id а если вложеность в 5 уровней. Вы будете 5 раз перебирать масив с коментариями?
Работоспособность не проверял, но думаю суть вы поймете.
$commentsTree = [];
$links = [];
foreach($comments as $comment){
if(array_key_exists($links, $comment['parent_id'])){
$parentComment = &$links[$comment['parent_id']];
$children = &$parentComment['children'][];
$children['comment'] = $comment;
$children['children'] = [];
}else{
$commentsTree[$comment['id']][];
$parentComment = &$commentsTree[$comment['id']];
$parentComment['comment'] = $comment;
$parentComment['children'] = [];
$links[$comment['id']] = &$parentComment;
}
}
всего в один проход мы можем собрать дерево, с учетом того что линейный набор данных отсортирован по parent_id или по дате.
По вашему алгоритму вы построете только 2 уровня корень и один дочерний. Вот в этом то и суть проблемы. Вы видимо не сталкивалися с деревьями?
внесу поправку:
перед }else{
забыл дописать:
$links[$comment['id']] = &$children;
Тут тоже проблема вы выбрали данные. Но какой то парент выбрался первым в $links его нет а значит он запишиться как корень. А это означает что алгоритм ошибочно привяжет комент где то в root. А не как дочерний элемент.
Если сортировать по parent_id такой проблемы не должно случиться.
Но я согласен это не совсем true way.
с другой стороны я не вижу проблемы в рекурсии на стороне приложения.
В идеале от sql мы должны получит что то вроде такого линейного представления:
1 | testing
1 | testing
2 | testing
2 | testing
1 | testing
где данные уже отсортированны
Вам правильно написали, можно построить дерево комментариев к посту в один проход без рекурсии. Только лучше использовать объекты, они передаются по ссылке и можно дополнительные переменные объявить один раз в самом классе.
public function getCommentsTree($post_id)
{
    $comments = Comment::find()->where(['post_id' => $post_id])->indexBy('id')->all();

    $topLevelComments = [];
    foreach ($comments as $comment) {
        if ($comment->parent_id) {
            $parentComment = $comments[$comment->parent_id];

            $comment->parentComment = $parentComment;
            $parentComment->childComments[$comment->id] = $comment;
        } else {
            $topLevelComments[$comment->id] = $comment;
        }
    }

    return $topLevelComments;
}

class Comment
{
    public $id;
    public $parent_id;
    public $post_id;
    // ...

    // заполняется снаружи при загрузке из базы
    public $parentComment = null;
    public $childComments = [];
}
Дерево теоретически можно хранить и в монго, таким образом, но писать дополнительные данные в сам документ с иерархией (напр общее кол-во, время последнего комментария и т.д.), а также обогащать данными другие сущности (напр. сущность пользователя- все его комментарии), но ИМХО- пока все лучше ложится на реляционную структуру.
НЛО прилетело и опубликовало эту надпись здесь
ltree — ух ты, спасибо большое!
Да postgresql умеет, но всё же пост не о postgre и не о проблеме построение деревьев а всё же о mongoDB.
P.S. Спасибо!
А почему mysql не любите?
MySql хороша по всем параметрам. Но когда у проэкта возрастает посещаимость и нагрузки. Начинаються проблемы производительностью:
  1. Код который делает перебор разбор данных, рендер и т.д.
  2. Маштабируемость, невозможно на уровне о.с. сделать, так что бы, было несколько серверов с mysql, и какойто сервер балансер который распределял бы запросы по серверам(разграничение нагрузки)
  3. Иногда приложению, не необходимо хранить структуру данных(допусти страницу в cms, понимаю грубый пример но всё же).

Я люблю разделение труда команды, т.е. php программист это один человек, mysql проэктироващик и программист это другой, и третьи лица фронтэнд разработчики. Такая команда добьёться большего результата. Работая в одном направление в месте.
Вот черт. Вы ещё и mysql не знаете. О чём с вами разговаривать?
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории