Комментарии для разных сущностей в Yii

Сущности

В теме о событиях натолкнулся на вопрос о том, как имея объект комментария на сайте можно получить ссылку на материал, к которому этот комментарий оставлен, при условии, что комментарии могут иметь привязку к разным сущностям. Сам я тоже использую в своих проектах комментарии к разнородным сущностям, поэтому хочу поделиться своей реализацией.

Представим, что в нашем проекте есть некоторые комментируемые сущности (Новость, Пост, Работа в портфолио, Рецепт и т.п.), для которых нужно сделать возможность оставлять комментарии. Для универсальности было бы лучше хранить все комментарии в одной таблице. Естественно предположить, что эта таблица для разделения комментариев должна содержать поля material_id и type.

Автор спрашивает:

...как в таком случае я получаю объект для которого был добавлен комментарий?

Задача (для примера): при добавлении нового комментария, мне необходимо проапдеэйтить запись для которой этот комментарий добавлялся (обновить счетчик комментариев в нем). Но такая модель не одна (Post, News, Blog и т.д для примера). Ну и хотелось бы сделать все это "красиво" без лишней завязки на сущности.

С одной комментируемой сущностью никаких проблем нет. Отношение к материалам можно прописать явно в классе комментария:

class Comment extends CActiveRecord
{
    // ...
 
    public function relations(){
        return array(
            'material' => array(self::BELONGS_TO, 'Post', 'material_id'),
        ));
    }
}

При наличии не только модели Post, но и ещё нескольких так уже сделать нельзя.

Конечно, в модели комментария можно сделать метод Comment::getMaterial(), который бы на основании типа данного комментария искал и возвращал необходимый объект:

class Comment extends CActiveRecord
{
    // ...
 
    public function getMaterial(){
        $className = $this->type;
        $material = CActiveRecord::model($className)->findByPk($this->material_id);
    }
}

Но тогда мы лишимся прелестей жадной загрузки Comment::model()->with('material')->findAll(), и число запросов к БД возрастёт до числа комментариев.

Для решения данной проблемы можно поступить следующим образом:

  1. Создать базовый класс Comment с общей логикой работы;
  2. Для каждой комментируемой сущности создать лёгкие подклассы NewsComment, PostComment и т.д., в которых объявить свой тип и свою связь с материалом;
  3. Настроить отношения сущностей к своим комментариям по соответствующим типам.

Итак, для комментариев создаём базовый класс Comment:

/**
 * This is the model class for table "{{comment}}".
 *
 * The followings are the available columns in table '{{comment}}':
 * @property string $id
 * @property string $type
 * @property integer $material_id
 * @property string $user_id
 * @property integer $parent_id
 * @property string $date
 * @property string $name
 * ...
 * @property string $text
 * @property integer $public
 * @property integer $moder
 */
class Comment extends CActiveRecord
{
    protected $type_of_comment = '';
 
    // ...
    public function tableName()
    {
        return '{{comment}}'; // Общая таблица
    }   
 
    // Для автовыбора классов при поиске
    protected function instantiate($attributes)
    {
        $class = $attributes['type'] . 'Comment'; // Класс выбирается по полю type
        $model = new $class(null);
        return $model;
    }
 
    public function rules()
    {
        return array(
            array('material_id, type, text', 'required'),
            // закрываем поля от комментатора
            array('material_id, type', 'unsafe', 'on'=>'insert'),
 
            array('material_id, user_id, type', 'safe', 'on'=>'search'),
            // ...
        );
    }
 
    public function relations()
    {
        return array(       
            // Автор (если есть)
            'user'=>array(self::BELONGS_TO, 'User', 'user_id'),
            // Для древовидных комментариев, если Вы их используете:
            // 'parent'=>array(self::BELONGS_TO, 'Comment', 'parent_id'),
            // 'children'=>array(self::HAS_MANY, 'Comment', 'parent_id'),
        );
    }
 
    public function search()
    {
        $criteria = new CDbCriteria;
 
        $criteria->compare('id',$this->id);
        if ($this->type_of_comment)
            $criteria->compare('type',$this->type_of_comment);
        else
            $criteria->compare('type',$this->type);
        $criteria->compare('material_id',$this->material_id);
        // ...
        $criteria->compare('public',$this->public);
        $criteria->compare('moder',$this->moder);
 
        return new CActiveDataProvider($this, array(
            'criteria'=>$criteria,
        ));
    }
 
    // scope
    public function material($id)
    {
        if ($id){
            $this->getDbCriteria()->mergeWith(array(
                'condition' => 'material_id=:id',
                'params'=>array(':id'=>$id),
            ));
        }
        return $this;
    }
 
    // scope
    public function type($type)
    {
        if ($type){
            $this->getDbCriteria()->mergeWith(array(
                'condition'=>'type=:type',
                'params'=>array(':type'=>$type),
            ));
        }
        return $this;
    }
 
    // переопределяем для поиска по типу
    public function find($condition='', $params=array())
    {
        $this->type($this->type_of_comment);
        return parent::find($condition, $params);
    }
 
    // переопределяем для поиска по типу
    public function findAll($condition='', $params=array())
    {
        $this->type($this->type_of_comment);
        return parent::findAll($condition, $params);
    }
 
    // переопределяем для поиска по типу
    public function findAllByAttributes($attributes, $condition='', $params=array())
    {
        $this->type($this->type_of_comment);
        return parent::findAllByAttributes($attributes, $condition, $params);
    }
 
    // переопределяем для поиска по типу
    public function count($condition='', $params=array())
    {
        $this->type($this->type_of_comment);
        return parent::count($condition, $params);
    }
 
    // храним $this->isNewRecord для проверки в afterSave()
    protected $_isNew; 
 
    protected function beforeSave()
    {
        $this->initType();
 
        // комментарии без типа в базу не пройдут!
        if (!$this->type) 
            return false;
 
        $this->_isNew = $this->isNewRecord;
        return parent::beforeSave();
    }
 
    protected function initType()
    {
        if (!$this->type)
            $this->type = $this->type_of_comment;
    }
 
    protected function afterSave()
    {
        if ($this->_isNew){
            $this->sendNotifications();
        }
        $this->updateMaterial();
        parent::afterSave();
    }
 
    protected function afterDelete()
    {
        $this->updateMaterial();
        parent::afterDelete();
    }
 
    // отправка уведомлений пользователям
    protected function sendNotifications()
    {
        // ...
    }
 
    // вызов обновления материала
    protected function updateMaterial()
    {
        if ($this->type && $this->material instanceof ICommentDepends){
            $this->material->updateCommentsState($this);
        }
    }
 
    // другие общие методы
    // ...
}

...и сколько угодно наследников (по числу наших комментируемых сущностей), в которых определены типы и ссылки на связанный материал:

class PostComment extends Comment
{
    // совпадает с префиксом подкласса комментария и именем класса сущности
    const TYPE_OF_COMMENT = 'Post';
 
    public static function model($className=__CLASS__){
        return parent::model($className);
    }
 
    public function __construct($scenario='insert'){
        // устанавливаем наш тип полю базового класса
        $this->type_of_comment = self::TYPE_OF_COMMENT;
        parent::__construct($scenario);
    }
 
    // Добавим ссылку на нашего специфического владельца
    public function relations(){ 
        return array_merge(parent::relations(), array(
            'material'=>array(self::BELONGS_TO, self::TYPE_OF_COMMENT, 'material_id'),
        ));
    }
}

Так как мы будем теперь работать с разными классами, то для универсальности (да и просто для удобства), создадим отдельную форму комментария:

class CommentForm extends CFormModel
{
    public $name;
    public $email;
    public $site;
    public $text;
 
    public function rules() {
        return array(
            array('text', 'required', 'message' => 'Напишите текст комментария'),
 
            array('name', 'length', 'max'=>255),
            array('name', 'required', 'message' => 'Представьтесь, пожалуйста', 'on'=>'anonim'),
 
            array('email', 'length', 'max'=>255),
            array('email', 'email', 'message' => 'Неверный формат E-mail адреса'),
            array('email', 'required', 'message' => 'Введите Email', 'on'=>'anonim'),
 
            array('site', 'url'),
            array('site', 'length', 'max'=>255),
        );
    }
 
    public function attributeLabels(){
        return array(
            'name' => 'Ваше имя',
            'email' => 'Ваш Email',
            'site' => 'Ваш сайт',
            'text' => 'Комментарий',
        );
    }
}

Теперь мы можем легко работать как с NewsComment, PostComment и т.д., так и непосредственно с базовым классом Comment:

// Вернёт все комментарии
$allComments = Comment::model()->findAll();
 
// Вернёт комментарии блога, так как мы переопределили метод findAll()
$postComments = PostComment::model()->findAll();
 
// Тоже вернёт комментарии блога, но уже тип можно выбирать динамически
$postComments = Comment::model()->type(PostComment::TYPE_OF_COMMENT)->findAll(); 
 
// Вернёт комментарии к записи блога, так как имена типа и класса сущности совпадают  omments = Comment::model()->type(get_class($post))->material($post->id)->findAll();
// Создаём комментарий нужного типа статически
$comment = new PostComment();
 
// Создаём комментарий нужного типа данимически
$comment = new Comment();
$comment->type = PostComment::TYPE_OF_COMMENT;

Добавление комментария для записи блога в контроллере может выглядеть так:

$commentForm = new CommentForm();
if (isset($_POST['CommentForm']){
    $commentForm->attributes = $_POST['CommentForm'];
    if ($commentForm->validate()){
        $comment = new PostComment();
        $comment->attributes = $_POST['CommentForm'];
        $comment->material_id = $post->id;
        if ($comment->save()){
            Yii::app()->user->setFlash('comment-form', 'Комментарий отправлен');
        }
    }
}

Теперь нам нужно немного видоизменить модели материалов, а именно в условия отношений добавить поиск по типу:

class Post extends CActiveRecord implements ICommentDepends
{
    public function relations()
    {
        return array(
            /* если пригодится отношение $model->comments, то раскомментируйте 
            'comments' => array(self::HAS_MANY, 'Comment', 'material_id',
                'condition'=>'comments.public=1 AND type=:type',
                'params'=>array(':type'=>PostComment::TYPE_OF_COMMENT),
                'order'=>'comments.id DESC'
            ),
            */
        );
    }
 
    // Реализуйте интерфейс ICommentDepends, если хотите получать оповещения от ваших комментариев
    // для своевременного обновления их числа в соответствующем поле или любых других целей
    public function updateCommentsState($comment)
    {
        $comments_count = BlogPostComment::model()->material($this->id)->count('public=1');
        $comments_new_count = BlogPostComment::model()->material($this->id)->count('public=1 AND moder=0');
 
        $this->updateByPk($this->id, array('comments_count' => $comments_count));
        $this->updateByPk($this->id, array('comments_new_count' => $comments_new_count));
    }
}

Интерфейс ICommentDepends:

interface ICommentDepends
{
    public function updateCommentsState($comment);
}

При выводе комментариев нам больше не нужно самим заботиться об их типах. При поиске экземпляров методами Comment::model()->findAll(), Comment::model()->find() и Comment::model()->findByPk() всю магическую работу выполняет переопределённый метод Comment::instantiate(), подсказанный в форуме. Именно он создаёт экземпляр нужного класса для каждого комментария в выборке.

Таким образом, отношение material будет всегда вести на экземпляр своей сущности, и мы можем работать с любыми комментариями полиморфно.

Вывод в одной ленте последних комментариев разных типов со ссылкой на якорь:

<?php
$criteria = new CDBCriteria();
$criteria->limit = 10;
$criteria->order = 'date DEC';
$criteria->with = array('material');
$comments = Comment::model()->findAll($criteria);
?>
<ul class="last_comments">
<?php foreach ($comments as $comment): ?>
    <li><a href="<?php echo $comment->material->url; ?>#comment_<?php echo $comment->id; ?>"><?php echo strip_tags($comment->text); ?></a></li>
<?php endforeach; ?>
</ul>

Для лёгкого получения ссылки $material->url нужно определить метод-геттер getUrl() в модели каждой сущности.

Для дальнейшего повышения универсальности полезно вывести добавление комментариев из кода контроллера в отдельный виджет. Это сильно облегчит организацию комментирования для новых сущностей.

class CommentsWidget extends СWidget
{
    public $material_id;
    public $type;
 
    protected $user;
 
    public function run()
    {
        if (!$this->user) $this->user = User::model()->findByPk(Yii::app()->user->getId());
        if (!$this->material_id) throw new CException('Not setted a Material_ID');
        if (!$this->type) throw new CException('Not setted a TYPE of comments');
 
        $form = new CommentForm();
        if (!$this->user) $form->scenario = 'anonim';
 
        if(isset($_POST['CommentForm']))
        {           
            $form->attributes = $_POST['CommentForm'];
 
            if($form->validate()){
 
                $className = $this->type . 'Comment';
 
                $comment = new $className;
                $comment->attributes = $_POST['CommentForm'];
                $comment->material_id = $this->material_id;
                $comment->public = 1;
                $comment->moder = 0;                
                if ($this->user)
                    $comment->user_id = $this->user->id;
 
                if ($comment->save()){
                    Yii::app()->user->setFlash('comment-form','Ваш коментарий добавлен');
                    Yii::app()->controller->refresh();
                }
            }
        }
 
        $comments = Comment::model()
            ->type($this->type)
            ->material($this->material_id)
            ->with('user')
            ->findAll(array('order'=>'t.id ASC'));
 
        $this->render('Comments/comments', array(
            'comments'=>$comments,
            'form'=>$form,
            'user'=>$this->user,
            'material_id'=>$this->material_id,
            'type'=>$this->type,
        ));
    }
}

Этот виджет в своих представлениях должен полностью содержать вывод комментариев и формы комментирования.

Таким образом, для добавления комментирования к записям блога необходимо всего лишь создать подкласс PostComment с типом Post и отношением material, и в представление view.php после вывода содержимого записи добавить вызов виджета комментариев:

<?php $this->widget('CommentsWidget', array(
    'material_id'=>$post->id,
    'type'=>PostComment::TYPE_OF_COMMENT,
)); ?>

И так со всеми сущностями. Код контроллера дополнять не нужно:

class PostController extends Controller
{
    // ...
 
    public function actionView()
    {
        $post = $this->loadModel();
 
        $this->render('view', array(
            'post'=>$post,
        ));
    }
}

Вспомним, что если сущность должна получать уведомление об изменениях среди её комментариев, то её модель должна реализовать интерфейс ICommentDepends.

Теперь все комментарии на сайте администратор может обслуживать в том же списке и работая с классом Comment как и раньше (используя тот же самый метод Comment::model()->with('material', 'user')->findAll() или CGridView с провайдером $model->search(), и те же отношения $comment->material и $comment->user) не обращая внимания на типы комментариев.

Другие статьи

В процессе работы с фреймворком часто приходится запрашивать у моделей всевозможные массивы (для генерации меню, для выпадающих списков в формах поиска или редактирования сущностей), а также находить нужную категорию по составному пути.

При администрировании многих разделов сайта возникла необходимость работы с логическими переключаемыми атрибутами модели (опубликована новость или нет, прочитано сообщение или нет и т.п.). В сети нашлось решение использовать колонку из чекбоксов с автосохранением данных Ajax запросом по событию onChange(). Эстетически выводить несколько колонок чекбоксов было не очень красиво. Было решено для наглядности сделать столбик из иконок-ссылок.

Давным давно в тридевя... в шаблонизаторе моей второй по счёту старой CMS вставка виджетов в шаблон была релизована посредством использования старой доброй клинописи вида {{WidgetName|param1=val1;param2=val2}}. Этот код можно было встявлять даже в текст, что давало неоценимую возможность компоновать страницы любой сложности из виджетов прямо в текстовом редакторе админки.

При выводе новостей и комментариев на публичном или личном сайте возникает необходимость фильтрации HTML-кода от опасных XSS элементов. Компонент HTML Purifier, поставляемый в комплекте с Yii Framework, может сильно облегчить задачу фильтрации. Это действительно мощный и гибко настраиваемый инструмент. Рассмотрим способы работы с ним.

Комментарии

 

Виталий Иванов

Дмитрий, поясни вот здесь:

// вызов обновления материала
protected function updateMaterial()
{
    if ($this->type && $this->material instanceof ICommentDepends){
        $this->material->updateCommentsState($this);
    }
}

откуда берется $this->material ?

Ответить

 

Дмитрий Елисеев

У каждого конкретного типа комментария есть отношение BELONGS_TO:

class PostComment extends Comment
{
    const TYPE_OF_COMMENT = 'Post';
    
    public function relations(){ 
        return array_merge(parent::relations(), array(
            'material'=>array(self::BELONGS_TO, self::TYPE_OF_COMMENT, 'material_id'),
        ));
    }
}

Соответственно $this->material ссылается здесь на запись блога Post.

Ответить

 

Виталий Иванов

Угу, спасибо, понял где у меня была ошибка. Я через контроллер сделал работу с комментариями, в результате чего несколько иначе создавался комментарий.
Спасибо Дмитрий, разобрался.

Ответить

 

Denis LED

Здравствуйте. Завис на моменте:

Теперь мы можем легко работать как с NewsComment, PostComment и т.д., так и непосредственно с базовым классом Comment:

Выводит без проблем:
$allComments = Comment::model()->findAll();

А вот тут:
$postComments = PostComment::model()->findAll();
Выбивает:
Fatal error: Maximum function nesting level of '500' reached, aborting! in Z:\home\test\www\framework\db\ar\CActiveRecord.php on line 154

1. Подскажите из-за чего может циклить.
2. Какое назначение у поля $moder

Ответить

 

Дмитрий Елисеев

Добавьте атрибут type_of_comment в родительский класс:

class Comment extends CActiveRecord
{
    protected $type_of_comment = '';
    ...

Его не должно быть в таблице.

Значение moder=0 у меня помечает новые комментарии.

Ответить

 

Денис Наталевич

Спасибо за ответ!
Теперь разобрался, ато напутал всё что можно :)

Ответить

 

alex

Спасибо за статью!
Не было бы лучше вынести updateCommentsState в yii behavior?

Ответить

 

Дмитрий Елисеев

Можно сделать. Например, передав поведению имена полей. При желании можно вообще обойтись без интерфейса, метода, поведения и разместить этот код в самом классе Comment:

protected function updateMaterial(){
    $this->matherial->updateByPk($this->matherial->getPrimaryKey(), array(
        'new_comments_count' => ...,
        'comments_count' => ...,
    ));
}
Ответить

 

alex

Но в таком случае material модель "обязана" будет содержать в себе оба поля для каунтов комментариев.

Ответить

 

Дмитрий Елисеев

В том и дело. Иначе можно именно сделать поведение, передать ему имена полей и потом проверять из комментария наличие у материала поведения или метода updateCommentsState «утиным» способом. А пока банально проверяется наличие интерфейса и вызовается метод. Модель сама решает, что ей делать.

Ответить

 

Сергей Зеленов

Дмитрий, такой вопрос. Почему бы не использовать именованные группы условий вместо наследования?
Например, что-то такое

public function scopes() {
    return array(
        'post'=>array(
            'condition'=>'type='.self::COMMENT_TYPE_POST,
        ),
    return array(
        'shop'=>array(
            'condition'=>'type='.self::COMMENT_TYPE_SHOP,
        ),
    );
}

и получать список через

Comment::model()->post()->findAll() 

А там можно и с наследованием сделать. Задать defaultScope, и через instantiate получать нужную модель.
Или я что-то неправильно понял?

Ответить

 

Дмитрий Елисеев

В принципе, можно обойтись и одним классом, добавив scopes(), sefaultScope(), и switch..case в relations() и щё что-то.

Ответить

 

Святослав

Дмитрий, такой вопрос. А если например сделать так.
В таблице с коментариями добавить несколько полей. Столько полей, сколько у нас будет типов записей с коментами.
Например будут поля:

post_id
news_id
product_id

И связать их по Foreign key
Соотвествено сделать это в каждой модели.
Будет ли нормально работать такой вариант?
Или будет создавать три объекта моделей вместо одного?

Ответить

 

Дмитрий Елисеев

Можно. Но тогда придётся подумать над тем, как в каждом случае выбирать нужное поле при создании комментария и в отношении material, используя конструкции if или switch в relations и в контроллерах, что будет не очень полиморфно.

Ответить

 

Святослав

А, понял. Спасибо.

Ответить

 

Святослав
// Вернёт комментарии к записи блога, так как имена типа и класса сущности совпадают  
$сomments = Comment::model()->type(class_name($post))->material($post->id)->findAll();
 

А что это за функция
class_name($post)?
Откуда она?
может нужно get_class($post)?

Ответить

 

Дмитрий Елисеев

Да, действительно get_class(). Исправил.

Ответить

 

Александр

Здравствуйте.

Который час пытаюсь разобраться, но ничего не получается...

Полностью скопировал ваш код, запускаю, получаю:

Fatal error: Cannot instantiate abstract class CActiveRecord in /var/www/dev1/data/www/khrdev.ru/yii/framework/db/ar/CActiveRecord.php on line 386

Если добавить к модели "Comment" вот это

public static function model($className=__CLASS__){
        return parent::model($className);
    }

То получаю исключение

Отношение "material" не было определено в active record классе "Comment".

Куда копать? Благодарю.

Ответить

 

Александр

Простите, забыл указать, в контроллере такой код:

$criteria = new CDbCriteria();
$criteria->limit = 10;
$criteria->order = 'date DEC';
$criteria->with = array('material');
$comments = Comment::model()->findAll($criteria);
Ответить

 

Дмитрий Елисеев

Уберите жадную загрузку:

$criteria->with = array('material');
Ответить

 

Valentin Zv

Есть еще варианты.
1) Использовать кросс-таблицы для каждой сущности
2) Использовать единый ключ для создания идентификаторов каждой сущности и тогда выборка комментариев однозначно будет указывать на id сущности, так как в таблице другой сущности не будет ключа с таким же значением.

Я сделал комментарии с кросс-таблицами, но сижу и думаю как реализовать древовидный вывод, нет у Вас подходящей статейки на эту тему?

Ответить

 

Дмитрий Елисеев

Рекурсивным выводом по parent_id. Можно для удобства добавить HAS_MANY отношение child_comments.

Ответить

 

Valentin Zv

Однако, тогда будет число запросов равным количеству веток

Ответить

 

Дмитрий Елисеев

Если через child, то да. Но я делаю без него одним запросом findAll() как в листинге виджета и в представлении рекурсивно обхожу полученный массив.

Ответить

 

Жан

Хотел спросить совет по поводу лайков для комментариев на вашем сайте. Достаточно ли хранить ip адрес для уникальности лайков или Вы еще храните строку useragent?
Спасибо

Ответить

 

Дмитрий Елисеев

Можно и хранить IP, и использовать Evercookie, если боитесь накрутки.

Ответить

 

Жан

А как вы определяете уникальность и храните в БД лайки, если не секрет?

Ответить

 

Дмитрий Елисеев

В базе не храню :) Просто сохраняю в сессию.

Ответить

 

Дмитрий

А вот возникает вопрос по поводу админской части данного модуля.
Допустим как нам получить список всех комментариев независимо от их типа и получить материал к которому они принадлежат?

Ответить

 

Дмитрий Елисеев

В Comment можно добавить метод getMaterial(), а в Post во все модели материалов добавить метод getUrl(), чтобы использовать $comment->material и $comment->material->url.

Ответить

 

Дмитрий

Как я понимаю что модель "Comment" лежит в модуле "Comment", а вот модель "PostComment" в модуле "Post" соответственно модель "NewsComment" в модуле "News". Возникает тогда вопрос как красивее импортнуть все модели "PostComment, NewsComment ..." в модуль "Comment"?

Ответить

 

Дмитрий Елисеев

А, точно, такой getMaterial с import я в сквозном поиске делал.

Ответить

 

Дмитрий

Плохо что не упоминается обработка комментариев от не авторизованных пользователей. Например, невидно куда сохраняются введенные данные "Имя, Email" пользователя?

Ответить

 

Владислав

А как в делаете так что имя и email вводим один раз, а потом поля всё время заполнены?

Ответить

 

Дмитрий Елисеев

Запоминанием в Cookies.

Ответить

 

Дмитрий

А где вы эти данные пишете в cookies, прямо в виджете?

Ответить

 

Дмитрий Елисеев

Всё делаю прямо перед сохранением комментария:

$comment = new Comment();

if (Yii::app()->user->isGuest) {
    $comment->attributes = $this->loadFormCookies();
}

if (isset($_POST['Comment'])) {
    $comment->attributes = $_POST['Comment'];

    $this->saveToCookies(array(
        'name' => $comment->name,
        'email' => $comment->email,
        'site' => $comment->site,
    ));

    ....

    if ($comment->save()) { ... }
}
Ответить

 

Дмитрий

Здравствуйте Дмитрий.
Давно хотел спросить, а почему вы используете такой вид связи:

// Родитель (для древовидных комментариев, если Вы их используете)
'parent'=>array(self::BELONGS_TO, 'Comment', 'parent_id'),

Если намного удобней использовать такой:
'children' => array(self::HAS_MANY, 'Comment', 'parent_id'),

С таким видом связи намного проще формировать дерево.
Или я что-то не так понял в вашем коде?

Ответить

 

Дмитрий Елисеев

Указываю и parent, и children. Добавил в статью.

Parent использую для уведомления автора верхнего комментария об ответе.

А дерево эффективнее формировать выбрав все элементы одним запросом findAll и уже обходя этот массив по parent_id рекурсией. А иначе, если использовать children, для каждого комментария будет по дополнительному запросу к базе.

Ответить

 

Дмитрий

По поводу рекурсии я с вами полностью согласен.

Ответить

 

خرید کریو

took me a while to figure out the content by google translate but it was worth it . the code pattern is unique

Ответить

 

Игорь Мастер

А как организовать дерево комментариев?

Ответить

 

Дмитрий Елисеев

Полем parent_id.

Ответить

 

боби фио

А потом как в стилях сделать, чтобы дочерние блоки смещались? И как сделать, чтобы передавался perent_id при нажатии на "ответить"?

Ответить

 

Дмитрий Елисеев

В стилях можно как здесь. А для ответов выводим идентификаторы комментариев:

<div class="comment" data-id="<?= $comment->id ?>">...</div>

И при клике по ссылке получаем id:

var id = $(this).closest('.comment').data('id');

и записываем в hidden-поле CommentForm[parent_id] в форме.

Ответить

 

боби фио

Спасибо. Ну и записываем в hidden также javascript ом?

Ответить

 

Дмитрий Елисеев

Да.

Ответить

 

боби фио

А каким методом Вы форму перемещаете под комментарий, при клике на ответ. Можете этот кусок кода показать.А то что-то не пойму, как это сделать.

Ответить

 

Дмитрий Елисеев
$(document).on('click', '.reply-link', function() {
    var comment = $(this).closest('.comment');
    $('#comment-form').detach().appendTo(comment);
    $('#comment-parent_id').val(comment.data('id'));
});
Ответить

 

боби фио

Спасибо. Попробую.

Ответить

 

боби фио

марджинами

Ответить

 

боби фио

Дмитрий. Подскажите, а как дату лучше формировать для вывода в блоках сообщений, ведь у каждого местное время свое. По местному например - 11.00 ,а на сервере 13.00. Чтобы выводилось актуальное время?

Ответить

 

Дмитрий Елисеев

В Yii2 выводите через formatter:

Yii::$app->formatter->asDateTime($model->created_at)

и по настройкам пользователя меняйте Yii::$app->formatter->timeZone.

В Yii1 похожее есть.

Ответить

 

боби фио

А возможно ли это автоматизировать. Не всегда удобно пользователю давать на выбор таймзону.
Кстати, сейчас при публикации комментария вижу вполне корректное время публикации, хотя таймзону не выбирал.

Ответить

 

Дмитрий Елисеев

У меня по московскому времени выводится. А так погуглите JavaScript timezone detect.

Ответить

 

боби фио

спс

Ответить

Оставить комментарий

Войти | Завести аккаунт


(никто не увидит)



Можно использовать теги <p> <ul> <li> <b> <i> <a> <pre>