Многоуровневая система комментариев 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:
Ответы
Ответ 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