Подтвердить что ты не робот

Многоуровневая система комментариев Laravel

У меня возникают трудности с рекурсивными частичными представлениями с лезвиями. Все работает по большей части с исключением рекурсии в файле comment.blade.php.

Я знаю, что мне нужно использовать foreach вокруг @include('articles.comments.comment', $comment) для вызова себя снова, но я не уверен, как вызвать его.

article_comments:

id
message
user_id
parent_id
created_at
updated_at

app\Article.php Класс:

class Article extends Model {

    protected $table = 'articles';

    protected $fillable = [
        'category',
        'title',
        'permalink',
        'synopsis',
        'body',
        'source',
        'show_author',
        'published_at'
    ];

    protected $dates = ['published_at'];

    public function scopePublished($query)
    {
        $query->where('published_at', '<=', Carbon::now());
    }

    public function setPublishedAtAttribute($date)
    {
        $this->attributes['published_at'] = Carbon::parse($date);
    }

    public function comments()
    {
        return $this->hasMany('App\Comments')->where('parent_id', 0);
    }

}

app\Comments.php Класс:

class Comments extends Model {

    protected $table = 'article_comments';

    protected $fillable = [
        'parent_id',
        'message',
    ];

    public function author() {
        return $this->belongsTo('App\User'); 
    }

    public function children()
    {
        return $this->hasMany('App\Comments', 'parent_id');
    }

    public function countChildren($node = null)
    {
        $query = $this->children();
        if (!empty($node)) {
            $query = $query->where('node', $node);
        }

        $count = 0;
        foreach ($query->get() as $child) {
            // Plus 1 to count the direct child
            $count += $child->countChildren() + 1; 
        }
        return $count;
    }

}

приложение\Http\Контроллеры\ArticlesController.php:

public function show($permalink)
{
    $article = Article::where('permalink', '=', $permalink)->with('comments','comments.author','comments.children')->first();
    if ($article != null) {
        $comments = $article->comments;
        return view('articles.show', compact('article','comments'));
    } else {
        return redirect('/')->with('error', 'This article does not exist.');
    }
}

ресурсы\вид\Статьи\show.blade.php

@if (count($comments) > 0)
    <ul>
    @foreach ($comments as $comment)
        @include('articles.comments.comment', ['comment'=>$comment])
    @endforeach
    </ul>
@else
    no comments
@endif

ресурсы\Views\Статьи\комментарии\comment.blade.php

<li>
    {{ $comment->message }}

    @if (count($comment->children) > 0)
        <ul>
        @foreach ($comment->children as $child)
            @include('articles.comments.comment', ['comment'=>$child])
        @endforeach
        </ul>
    @endif
</li>

Текущая ошибка:

Invalid argument supplied for foreach() (View: /var/www/dev.example.com/resources/views/articles/comments/comment.blade.php) (View: 
4b9b3361

Ответ 1

Ты довольно близко. Я думаю, что это должно сработать:

ресурсы\вид\Статьи\show.blade.php

@if (count($comments) > 0)
    <ul>
        @each('articles.comments.comment', $comments, 'comment');
    </ul>
@else
    no comments
@endif

ресурсы\Views\Статьи\комментарии\comment.blade.php

<li>
    {{ $comment->message }}

    @if (count($comment->children) > 0)
        <ul>
            @each('articles.comments.comment', $comment->children, 'comment');
        </ul>
    @endif
</li>

приложение\Http\Контроллеры\ArticlesController.php:

$article = Article::where('permalink', '=', $permalink)->with('comments','comments.author','comments.children')->first();
$comments = $article->comments;
return view('articles.show', compact('article','comments'));

Ответ 2

Во-первых, я вижу, что вы оставляете все комментарии на одной странице без какой-либо разбивки на страницы, но вы ограничиваете свой метод comments() в модели Article только комментариями, имеющими parent_id=0, чтобы получать только корневые комментарии, Но, глядя дальше на свой код, в вашем контроллере вы ленитесь загружать comments.author и comments.children. Обратите внимание, что ленивая загрузка будет происходить только для первых родительских комментариев, и после этого всем детям придется делать много запросов, чтобы получить свои отношения, охотно загружая отношения.

Если вы хотите разместить все комментарии на одной странице, я предлагаю удалить ограничение parent_id=0 и загрузить комментарии сразу и применить стратегию для их заказа. Вот пример:

приложение \Article.php

class Article extends Model {

    // ...

    public function comments()
    {
        return $this->hasMany('App\Comments');
    }

}

приложение\Http\Контроллеры\ArticlesController.php

use Illuminate\Database\Eloquent\Collection; 

// ...

public function show($permalink)
{
    $article = Article::where('permalink', '=', $permalink)->with('comments','comments.author','comments.children')->first();

    if ($article != null) {

        $comments = $this->buildCommentTree($article->comments);

        return view('articles.show', compact('article','comments'));
    } else {
        return redirect('/')->with('error', 'This article does not exist.');
    }
}


protected function buildCommentTree(Collection $comments, $root = 0)
{
    $branch = new Collection();

    $comments->each(function(Comment $comment) use ($root) {
        if ($comment->parent_id == $root) {

            $children = $this->buildCommentTree($comments, $comment->getKey());

            $branch->push($comment);
        } else {
            // This is to guarantee that children is always a Collection and to prevent Eloquent 
            // from hitting on the database looking for the children
            $children = new Collection();
        }

        $comment->setRelation('children', $children);
    });

    return $branch;

}

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

ресурсы\вид\Статьи\show.blade.php

@if (!$comments->isEmpty())
    @include("articles.comments.container", ['comments'=>$comments])
@else
    no comments
@endif

ресурсы\Views\Статьи\комментарии\container.blade.php

@if (!$comments->isEmpty())
    <ul>
    @foreach ($comments as $comment)
        @include('articles.comments.show', ['comment'=>$comment])
    @endforeach
    </ul>
@else

ресурсы\Views\Статьи\комментарии\show.blade.php

<li>
    {{ $comment->message }}

    @if (!$comment->children->isEmpty())
        @include("articles.comments.container", ['comments'=>$comment->children])
    @endif
</li>

Ответ 3

Возможно, я что-то пропустил, но мне кажется, что у вас слишком сложный код. Например, вы можете добавить отношение user к вашей модели комментариев (вы также можете удалить весь метод count):

public function user()
{
    return $this->belongsTo('App\User');
}

Затем, когда вам нужно получить доступ к идентификатору пользователя или имени пользователя комментатора, вы можете просто вызвать:

$user_id = $comment->user->id;
$username = $comment->user->username;

Затем вы сможете очистить много своего контроллера:

$article = Article::where('permalink', '=', $permalink)->first();
$comments = Comments::where('article_id', '=', $article->id)->get();

Ваш файл show.blade.php должен быть как можно точнее. Ваш файл comment.blade.php можно записать следующим образом:

<li>{{ $comment->message }}</li>
@if ($comment->children->count() > 0)
    <ul>
        @foreach($comment->children as $child)
            @include('articles.comments.comment', $child)
        @endforeach
    </ul>
@endif

Я считаю, что ваша проблема с ошибкой Invalid argument supplied for foreach(), которую вы получаете, связана с вашим исходным запросом на комментарии. Ошибка должна быть исправлена ​​с использованием правильных реляционных моделей.

В качестве дополнительной заметки, если вам нужно подсчитать ваши комментарии в столбце node, вы должны сделать что-то вроде этого:

$comment->children()->where('node', $node)->get()->count();

Ответ 4

Я немного упростил это, но это то, что я сделал бы:

Красноречивая часть:

class Article extends Model
{
    public function comments()
    {
        return $this->hasMany(Comment::class)->where('parent_id', 0);
    }
}

class Comment extends Model
{
    public function article()
    {
        return $this->belongsTo(Article::class);
    }

    public function children()
    {
        return $this->hasMany(Comment::class, 'parent_id');
    }

    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

Элемент действия контроллера/маршрута:

// Fetching article using your example:
$article = Article::where('permalink', '=', $permalink)->first();

// returning view somewhere below...

Просмотр части: Вид статьи:

@include('comments', ['comments' => $article->comments])

Просмотр комментариев:

<ul>
    @foreach($comments as $comment)
        <li>
            <h2>{{ $comment->user->name }}</h2>
            <p>{{ $comment->message }}</p>
            @if(count($comments) > 0)
                @include('comments', ['comments' => $comment->children])
            @endif
        </li>
    @endforeach
</ul>

Обратите внимание, что это не очень эффективно, если есть много вложенных комментариев, таких как:

Comment
  -> Child Comment
    -> Child Child Comment
      -> Child Child Child Comment

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

// Change the relation on the Article Eloquent model:
public function comments()
{
    return $this->hasMany(Comment::class);
}

Предполагая, что значение по умолчанию для parent_id равно 0, то в представлении вашей статьи вы можете сделать что-то вроде этого:

@include('comments', ['comments' => $article->comments, 'parent' => 0])

И в вашем представлении комментариев вы можете сделать что-то вроде этого:

<ul>
    @foreach($comments as $comment)
        @if($parent === (int) $comment->parent_id)
            <li>
                <h2>{{ $comment->user->name }}</h2>
                <p>{{ $comment->message }}</p>
                @include('comments', ['comments' => $comments, 'parent' => (int) $comment->id])
            </li>
        @endif
    @endforeach
</ul>

Обновление: Посмотрев, как вы все еще придерживаетесь этого, я добавил пример: https://github.com/rojtjo/comments-example

Убедитесь, что вы проверили последнюю фиксацию, она показывает разницу между различными методами, которые я использовал в примерах.

Ответ 5

Начиная с базы данных mysql:

Структура таблицы комментариев

id
commentable_id
commentable_type
parent_id
message
user_id
created_at
updated_at

Структура таблицы статей

id
content
user_id
created_at
updated_at

Ваши модели будут настроены как таковые: модель комментариев будет полиморфной, чтобы вы могли использовать свои комментарии в других разделах вашего приложения. Примечание. Я рекомендую переместить ваши модели в папку app\models\.

app\Comments.php Класс:

namespace App

class Comments extends \Eloquent {

    /**
     * Name of the table to use for this model
     *
     * @var string
     */
    protected $table = 'comments';

    /**
     * The fields that are fillable
     *
     * @var array
     */
    protected $fillable = array(
        'commentable_type',
        'commentable_id',
        'message',
        'user_id',
        'parent_id'
    );

    /**
     * Commentable: Polymorphic relationship
     *
     * @return mixed
     */
    public function commentable()
    {
        return $this->morphTo();
    }

    /**
     * User: belongsTo relationship
     *
     * @return mixed
     */
    public function user()
    {
        return $this->belongsTo('User','user_id');
    }

    /**
    * Parent Comment: belongsTo relationship
    *
    * @return \App\Comment | null
    */
    public function parent()
    {
        return $this->belongsTo('App\Comments','parent_id');
    }

    /**
    * Children Comment: hasMany relationship
    *
    * @return \Illuminate\Database\Eloquent\Collection
    */        
    public function children()
    {
        return $this->hasMany('App\Comments','parent_id');
    }
}

app\Articles.php класс:

namespace App

class Articles extends \Eloquent {
    /**
     * Name of the table to use for this model
     *
     * @var string
     */
    protected $table = 'articles';

    protected $fillable = [
        'user_id',
        'content'
    ];

    /**
     * User: belongsTo relationship
     *
     * @return mixed
     */
    public function user()
    {
        return $this->belongsTo('User','user_id');
    }

    /**
    * Comments: Polymorphic Relationship
    *
    * @return \Illuminate\Database\Eloquent\Collection
    */
    public function comments()
    {
        return $this->morphMany('App\Comments','commentable');
    }

}

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

приложение\Http\Контроллеры\ArticlesController.php:

namespace App\Http\Controllers;

use App\Articles;

class ArticlesController extends Controller {
    /**
    * GET: View of an article
    *
    * @param string $permalink
    * @return \Illuminate\View\View
    */
    public function getView($permalink)
    {
        //Let fetch the article
        $article = Articles::where('permalink','=',$permalink)
                    //Eager load relationships while applying conditions
                    ->with([
                    'comments' => function($query) {
                        //Utility method to orderBy 'created_at' DESC
                        return $query->latest();
                    },
                    'comments.children' => function($query) {
                        //Utility method to orderBy 'created_at' DESC
                        return $query->latest();
                    }
                    ])
                    ->first();

        //Generate the view
        return view('articles.show',['article'=>$article]);
    }
}

Наконец, представления для отображения ваших данных

ресурсы\вид\Статьи\show.blade.php

<h1>My article:</h1>
<div>
    <p>
        @if($article)
            {{$article->content}}
        @endif
    </p>
</div>

@if($article)
    <!-- Comments: START -->
    <div>
        @if (count($article->comments) > 0)
            <ul>
            @foreach ($article->comments as $comment)
                @include('articles.comments.comment', ['comment'=>$comment])
            @endforeach
            </ul>
        @else
            <span>no comments</span>
        @endif
    </div>
    <!-- Comments: END -->
@endif

ресурсы\Views\Статьи\комментарии\comment.blade.php

<li>
    {{ $comment->message }}
    <!-- Comment Children: START -->
    @if (count($comment->children) > 0)
        <ul>
            @foreach ($comment->children as $child)
                @include('articles.comments.comment', ['comment'=>$child])
            @endforeach
        </ul>
    @endif  
    <!-- Comment Children: END -->
</li>

Ответ 6

Я как бы отредактировал ваши комментарии. Вы можете попробовать следующее:

приложение \comments.php

class Comments extends Model {

protected $table = 'article_comments';

protected $fillable = [
    'parent_id',
    'message',
];

public function children()
{
    return $this->hasMany('App\Comments', 'parent_id');
}

public function countChildren($node = null)
{
    $query = $this->children();
    return (!empty($node)) ? $query->where('node', $node)->count() : $query->count();
}
}

ресурсы\Views\Статьи\комментарии\comment.blade.php

<li>{{ $comment->message }}</li>  
@if (App\Comments::find($comment->comment_id)->countChildren() > 0)
<ul>
    <li>{{ $comment->message}}</li>
    @include('articles.comments.comment', ['comment' => $comment])
</ul>
@endif

ресурсы\вид\Статьи\show.blade.php

@if (count($comments) > 0)
<ul>
    @foreach ($comments as $comment)
      @include('articles.comments.comment', ['comment' => $comment])
    @endforeach
</ul>
@else
 no comments
@endif