本篇文章中我将会使用一些 Laravel 的高级功能,这些高级功能对新手理解系统是不利的,但熟手使用这些功能可以大幅提升开发效率。

前面我们已经说过,Laravel Eloquent ORM 是 Laravel 中最强大的部分,也是 Laravel 能如此流行最重要的原因。中文文档在:https://d.laravel-china.org/docs/5.5/eloquent

就是一个最简单的 Eloquent Model 类:

若想进一步了解 Eloquent 推荐阅读系列文章:

我们需要新建一个表专门用来存放每一条评论,每一条评论都属于某一篇文章。评论之间的层级关系比较复杂,本文为入门教程,主要是为了带领大家体验模型间关系,就不再做过多的规划了,将“回复别人的评论”暂定为简单的在评论内容前面增加 @johnlui 这样的字符串。

建立 Model 类和数据表

创建名为 Comment 的 Model 类,并顺便创建附带的 migration,在 learnlaravel5 目录下运行命令:

  1. php artisan make:model Comment -m

这样一次性建立了 Comment 类和 两个文件。填充该文件的 up 方法,给 表增加字段:

  1. public function up()
  2. {
  3. Schema::create('comments', function (Blueprint $table) {
  4. $table->increments('id');
  5. $table->string('nickname');
  6. $table->string('email')->nullable();
  7. $table->string('website')->nullable();
  8. $table->text('content')->nullable();
  9. $table->integer('article_id');
  10. $table->timestamps();
  11. });
  12. }

之后运行命令:

  1. php artisan migrate

去数据库里瞧瞧,comments 表已经躺在那儿啦。

在 Article 模型中增加一对多关系的函数:

搞定啦!Eloquent 中模型间关系就是这么简单!

构建前台 UI

让我们修改前台的视图文件,想办法把评论功能加进去。

创建前台的 ArticleController 类

运行命令:

  1. php artisan make:controller ArticleController

增加路由:

  1. Route::get('article/{id}', 'ArticleController@show');

此处的 {id} 指代任意字符串,在我们的规划中,此字段为文章 ID,为数字,但是本行路由却会尝试匹配所有请求,所以当你遇到了奇怪的路由调用的方法跟你想象的不一样时,记得检查路由顺序。路由匹配方式为前置匹配:任何一条路由规则匹配成功,会立刻返回结果,后面的路由便没有了响应的机会。

给 ArticleController 增加 show 函数:

  1. public function show($id)
  2. {
  3. return view('article/show')->withArticle(Article::with('hasManyComments')->find($id));
  4. }

别忘了在顶部引入 Model 类,否则会报类找不到的错误:

创建前台文章展示视图

新建 文件:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1">
  7.  
  8. <title>Learn Laravel 5</title>
  9.  
  10. <link href="//cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
  11. <script src="//cdn.bootcss.com/jquery/2.2.4/jquery.min.js"></script>
  12. <script src="//cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
  13. </head>
  14.  
  15.  
  16. <h4>
  17. <a href="/"><< 返回首页</a>
  18. </h4>
  19.  
  20. <h1 style="text-align: center; margin-top: 50px;">{{ $article->title }}</h1>
  21. <hr>
  22. <div id="date" style="text-align: right;">
  23. {{ $article->updated_at }}
  24. </div>
  25. <div id="content" style="margin: 20px;">
  26. <p>
  27. {{ $article->body }}
  28. </p>
  29. </div>
  30.  
  31. <div id="comments" style="margin-top: 50px;">
  32.  
  33. @if (count($errors) > 0)
  34. <div class="alert alert-danger">
  35. <strong>操作失败</strong> 输入不符合要求<br><br>
  36. {!! implode('<br>', $errors->all()) !!}
  37. </div>
  38. @endif
  39.  
  40. <div id="new">
  41. <form action="{{ url('comment') }}" method="POST">
  42. {!! csrf_field() !!}
  43. <input type="hidden" name="article_id" value="{{ $article->id }}">
  44. <div class="form-group">
  45. <label>Nickname</label>
  46. <input type="text" name="nickname" class="form-control" style="width: 300px;" required="required">
  47. </div>
  48. <div class="form-group">
  49. <label>Email address</label>
  50. <input type="email" name="email" class="form-control" style="width: 300px;">
  51. </div>
  52. <div class="form-group">
  53. <label>Home page</label>
  54. <input type="text" name="website" class="form-control" style="width: 300px;">
  55. </div>
  56. <div class="form-group">
  57. <label>Content</label>
  58. <textarea name="content" id="newFormContent" class="form-control" rows="10" required="required"></textarea>
  59. </div>
  60. </form>
  61. </div>
  62.  
  63. <script>
  64. function reply(a) {
  65. var nickname = a.parentNode.parentNode.firstChild.nextSibling.getAttribute('data');
  66. var textArea = document.getElementById('newFormContent');
  67. textArea.innerHTML = '@'+nickname+' ';
  68. }
  69. </script>
  70.  
  71. <div class="conmments" style="margin-top: 100px;">
  72. @foreach ($article->hasManyComments as $comment)
  73.  
  74. <div class="one" style="border-top: solid 20px #efefef; padding: 5px 20px;">
  75. <div class="nickname" data="{{ $comment->nickname }}">
  76. @if ($comment->website)
  77. <a href="{{ $comment->website }}">
  78. <h3>{{ $comment->nickname }}</h3>
  79. </a>
  80. @else
  81. <h3>{{ $comment->nickname }}</h3>
  82. @endif
  83. <h6>{{ $comment->created_at }}</h6>
  84. </div>
  85. <div class="content">
  86. <p style="padding: 20px;">
  87. {{ $comment->content }}
  88. </p>
  89. </div>
  90. <div class="reply" style="text-align: right; padding: 5px;">
  91. <a href="#new" onclick="reply(this);">回复</a>
  92. </div>
  93. </div>
  94.  
  95. @endforeach
  96. </div>
  97. </div>
  98.  
  99. </div>
  100.  
  101. </body>
  102. </html>

我们需要创建一个 CommentsController 控制器,并增加一条“存储评论”的路由。运行命令:

  1. php artisan make:controller CommentController

控制器创建成功,接下来我们增加一条路由:

  1. Route::post('comment', 'CommentController@store');

给这个类增加 store 函数:

此处 Comment 类请自己引入。

批量赋值

给 Comment 类增加 $fillable 成员变量:

    检查成果

    前台文章展示页:

    提交几条评论之后:

    2017 版 Laravel 系列入门教程(五)【最适合中国人的 Laravel 教程】 - 图2

    恭喜你,前台评论功能构建完成!

    评论跟 Article 一样,是一种可以管理的资源列表。2015 版教程的最后,我风风火火地罗列了一堆又一堆的代码,其实对还没入门的人几乎没用。在此,我将这个功能作为大作业布置给大家。大作业嘛,当然是没有标准答案的,但有效果图:

    2017 版 Laravel 系列入门教程(五)【最适合中国人的 Laravel 教程】 - 图4

    在做这个大作业的过程中,你将会反复地回头去看前面的教程,反复地阅读中文文档,会仔细阅读我的代码,等你完成大作业的时候,Laravel 就真正入门啦~~