【问题标题】:Multi-tiered comment system Laravel多层评论系统 Laravel
【发布时间】:2016-02-11 11:10:47
【问题描述】:

我在使用刀片递归部分视图时遇到了困难。除了 comment.blade.php 文件中的递归之外,大部分情况下一切正常。

我知道我需要在 @include('articles.comments.comment', $comment) 周围使用一个 foreach 来再次调用它自己,但我不知道如何调用它。

article_cmets 表:

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;
    }

}

app\Http\Controllers\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

resources\views\articles\cmets\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: 

【问题讨论】:

  • 您确定将$comments 传递给控制器​​中的视图吗? return view('articles.show', compact('comments'));
  • @OP 我看到你已经扩展了你的赏金。您能否告诉我们更新您的类以使用标准的 eloquent 关系(而不是本地查询)对问题有何影响?
  • 好的,在你的帖子中它说你有一个comments 表,但它显示Comment 类说$table = "article_comments",你的 cmets 表的实际名称是什么?
  • @OP 我做了一些挖掘,看起来刀片中有一个名为@each 的内置方法可以自然地处理其中的一些问题。你可以用我更新的答案试一试。如果这不起作用,我们需要 dd($comment-&gt;children) 看看我们在 foreach 中添加了哪些 laravel 似乎不喜欢的内容。
  • @Jeff @each 方法返回了同样的错误。将dd() 函数放在@foreach 中会返回一个字符串“1”。 (非常感谢您的持续努力)

标签: php laravel recursion laravel-5


【解决方案1】:

你已经很接近了。我认为这应该可行:

资源\视图\文章\show.blade.php

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

resources\views\articles\cmets\comment.blade.php

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

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

app\Http\Controllers\ArticlesController.php:

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

【讨论】:

  • Invalid argument supplied for foreach()
  • 我的children() 函数有问题吗?我写了{{ count($comment-&gt;children) }},他们都返回1。如果我使用{{ App\Comments::find($comment-&gt;comment_id)-&gt;countChildren() }},它会返回适当的金额。
  • 修复了@include 语句以正确传递变量。见:laravel.com/docs/5.1/blade#control-structures
  • 在您的 ArticlesController.php 中,您正在通过自定义查询构建器加载 cmets。相反,您应该使用诸如Article::where('permalink', '=', $permalink)-&gt;with('comments','comments.children','comments.user','comments.children.user')-&gt;first() 之类的文章查询来急切地加载 cmets。根据需要设置您的关系。
  • 在您的评论类中添加public function author() { return $this-&gt;belongsTo('App\User'); }。然后,当您获取 cmets 时,它只是 $article-&gt;comments,而用户数据只是 $comment-&gt;author-&gt;username。 Mysteryos 是对的,这是该查询的问题,只需使用关系
【解决方案2】:

首先,我看到您将所有 cmets 放在一个页面中而没有任何分页,但是,您将 Article 模型上的 comments() 方法限制为仅具有 parent_id=0 的 cmets 以仅获取根 cmets .但是,进一步查看您的代码,在您的控制器中,您正在延迟加载 comments.authorcomments.children。请注意,延迟加载只会发生在第一个父 cmets 上,之后,所有的子节点都必须做很多查询才能通过急切地加载关系来获取它们的关系。

如果您仍然想将所有 cmets 放在一个页面中,我建议您删除 parent_id=0 约束并一次加载所有 cmets 并应用策略来订购它们。这是一个例子:

app\Article.php

class Article extends Model {

    // ...

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

}

app\Http\Controllers\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;

}

现在我们已经对所有 cmets 进行了排序而不会影响数据库,我们可以开始循环遍历广告。我更喜欢使用两个文件而不是一个来循环,这样我以后可以重用代码。

资源\视图\文章\show.blade.php

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

resources\views\articles\cmets\container.blade.php

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

resources\views\articles\cmets\show.blade.php

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

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

【讨论】:

    【解决方案3】:

    我可能遗漏了一些东西,但在我看来,您的某些代码过于复杂。例如,您可以在 cmets 模型中添加 user 关系(您也可以删除整个 count 方法):

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

    然后,当您需要访问评论者的用户 ID 或用户名时,您只需调用:

    $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() 错误问题与您对 cme​​ts 的原始查询有关。应该使用适当的关系模型来修复错误。

    附带说明,如果您需要通过 node 列来计算评论的子项,您应该能够执行以下操作:

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

    【讨论】:

    • 试过你在这里说的一切。 cmets.blade.php 视图中的@if ($comment-&gt;children-&gt;count() &gt; 0) 似乎不喜欢这条线。返回错误信息Call to a member function count() on a non-object
    • 尝试更新您的评论的children 与此的关系:return $this-&gt;hasMany('App\Comments', 'parent_id')-&gt;with('children');。这将递归调用子关系并可能解决问题。
    【解决方案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>
    

    请注意,如果有很多嵌套的 cmets,这不是很有效,例如:

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

    另一种结构方式是一次性获取所有 cmets:

    // 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])
    

    在您的 cmets 视图中,您可以执行以下操作:

    <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
      

      您的模型将这样设置:评论模型将是多态的,因此您可以在应用程序的其他部分使用您的 cmets。注意:我建议将您的模型移动到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');
          }
      
      }
      

      article 控制器实际上与添加的代码相同,以预先加载 cmets 及其子项。

      app\Http\Controllers\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's 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
      

      resources\views\articles\cmets\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。你可以试试这个:

        app\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();
        }
        }
        

        resources\views\articles\cmets\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
        

        【讨论】:

          猜你喜欢
          • 2010-09-13
          • 2011-12-05
          • 1970-01-01
          • 2016-05-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-10-13
          • 2011-08-16
          相关资源
          最近更新 更多