johnlui / Learn-Laravel-5

Laravel 5 系列入门教程

Home Page:https://github.com/johnlui/Learn-Laravel-5/issues

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

2016 版 Laravel 系列入门教程(五)【最适合**人的 Laravel 教程】

johnlui opened this issue · comments

本教程示例代码见:https://github.com/johnlui/Learn-Laravel-5

在任何地方卡住,最快的办法就是去看示例代码。

本文是本系列教程的完结篇,我们将一起给 Article 加入评论功能,让游客在前台页面可以查看、提交、回复评论,并完成后台评论管理功能,可以删除、编辑评论。Article 和评论将使用 Laravel Eloquent 提供的“一对多关系”功能大大简化模型见关系的开发复杂度。最终,我们将得到一个个人博客系统的雏形,并布置一个大作业,供大家实战练习。

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

回顾 Eloquent

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

learnlaravel5/app/Article.php 就是一个最简单的 Eloquent Model 类:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Article extends Model
{
    //
}

若想进一步了解 Eloquent,推荐阅读系列文章:深入理解 Laravel Eloquent

开始构建评论系统

基础规划

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

建立 Model 类和数据表

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

php artisan make:model Comment -m

这样一次性建立了 Comment 类和 2016_06_03_220325_create_comments_table 两个文件。修改 migration 文件的 up 方法为:

public function up()
{
    Schema::create('comments', function (Blueprint $table) {
        $table->increments('id');
        $table->string('nickname');
        $table->string('email')->nullable();
        $table->string('website')->nullable();
        $table->text('content')->nullable();
        $table->integer('article_id');
        $table->timestamps();
    });
}

之后运行命令:

php artisan migrate

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

建立“一对多关系”

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

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Article extends Model
{
    public function hasManyComments()
    {
        return $this->hasMany('App\Comment', 'article_id', 'id');
    }
}

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

模型间关系中文文档:http://laravel-china.org/docs/5.1/eloquent-relationships
扩展阅读:深入理解 Laravel Eloquent(三)——模型间关系(关联)

构建前台 UI

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

创建前台的 ArticleController 类

运行命令:

php artisan make:controller ArticleController

增加路由:

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

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

给 ArticleController 增加 show 函数:

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

创建前台文章展示视图

新建 learnlaravel5/resources/views/article/show.blade.php 文件:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>Learn Laravel 5</title>

    <link href="//cdn.bootcss.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet">
    <script src="//cdn.bootcss.com/jquery/1.11.1/jquery.min.js"></script>
    <script src="//cdn.bootcss.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
</head>

    <div id="content" style="padding: 50px;">

        <h4>
            <a href="/"><< 返回首页</a>
        </h4>

        <h1 style="text-align: center; margin-top: 50px;">{{ $article->title }}</h1>
        <hr>
        <div id="date" style="text-align: right;">
            {{ $article->updated_at }}
        </div>
        <div id="content" style="margin: 20px;">
            <p>
                {{ $article->body }}
            </p>
        </div>

        <div id="comments" style="margin-top: 50px;">

            @if (count($errors) > 0)
                <div class="alert alert-danger">
                    <strong>操作失败</strong> 输入不符合要求<br><br>
                    {!! implode('<br>', $errors->all()) !!}
                </div>
            @endif

            <div id="new">
                <form action="{{ url('comment') }}" method="POST">
                    {!! csrf_field() !!}
                    <input type="hidden" name="article_id" value="{{ $article->id }}">
                    <div class="form-group">
                        <label>Nickname</label>
                        <input type="text" name="nickname" class="form-control" style="width: 300px;" required="required">
                    </div>
                    <div class="form-group">
                        <label>Email address</label>
                        <input type="email" name="email" class="form-control" style="width: 300px;">
                    </div>
                    <div class="form-group">
                        <label>Home page</label>
                        <input type="text" name="website" class="form-control" style="width: 300px;">
                    </div>
                    <div class="form-group">
                        <label>Content</label>
                        <textarea name="content" id="newFormContent" class="form-control" rows="10" required="required"></textarea>
                    </div>
                    <button type="submit" class="btn btn-lg btn-success col-lg-12">Submit</button>
                </form>
            </div>

            <script>
            function reply(a) {
              var nickname = a.parentNode.parentNode.firstChild.nextSibling.getAttribute('data');
              var textArea = document.getElementById('newFormContent');
              textArea.innerHTML = '@'+nickname+' ';
            }
            </script>

            <div class="conmments" style="margin-top: 100px;">
                @foreach ($article->hasManyComments as $comment)

                    <div class="one" style="border-top: solid 20px #efefef; padding: 5px 20px;">
                        <div class="nickname" data="{{ $comment->nickname }}">
                            @if ($comment->website)
                                <a href="{{ $comment->website }}">
                                    <h3>{{ $comment->nickname }}</h3>
                                </a>
                            @else
                                <h3>{{ $comment->nickname }}</h3>
                            @endif
                            <h6>{{ $comment->created_at }}</h6>
                        </div>
                        <div class="content">
                            <p style="padding: 20px;">
                                {{ $comment->content }}
                            </p>
                        </div>
                        <div class="reply" style="text-align: right; padding: 5px;">
                            <a href="#new" onclick="reply(this);">回复</a>
                        </div>
                    </div>

                @endforeach
            </div>
        </div>

    </div>

</body>
</html>

构建评论存储功能

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

php artisan make:controller CommentController

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

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

给这个类增加 store 函数:

public function store(Request $request)
{
    if (Comment::create($request->all())) {
        return redirect()->back();
    } else {
        return redirect()->back()->withInput()->withErrors('评论发表失败!');
    }
}

批量赋值

我们采用批量赋值方法来减少存储评论的代码,批量赋值中文文档

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

protected $fillable = ['nickname', 'email', 'website', 'content', 'article_id'];

检查成果

前台文章展示页:

提交几条评论之后:

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

【大作业】构建后台评论管理功能

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

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

问下提交表单的时候验证失败返回,数据依然保留在输入框怎么处理?

{{ old('xxx') }}

@johnlui 期待能讲一下多用户角色权限方面的解决方案,谢谢

protected $fillable = ['nickname', 'email', 'website', 'content', 'page_id'];
这里有个笔误。page_id应该是article_id

@MomoFu 👍 感谢

commented

@MomoFu 刚才遇见写下评论不能显示的问题,将page_id改为article就可以了,感谢

学习完感觉很有帮助,感谢!

commented

写的好

commented

垃圾 看了还是云里雾里 你应该说说artsion应该怎么用,配置文件规则是什么,而不是搞这些没用的

我觉得还是很清晰,对于新手是个不错的选择。

谢谢

commented

非常感谢,写的很不容易,大概了解了。

非常感谢,接触一个新框架的时候很需要这样一个小案例!

commented

ArticleController.php 要加上
use App\Article; use App\Comment;

commented

程序报错 MassAssignmentException in Model.php line 452: _token

错误原因:对应的Model里面下面这行出错了
protected $fillable = ['nickname', 'email', 'website', 'content', 'article_id'];

支持一下

commented

程序报错 MassAssignmentException in Model.php line 452: _token的同学注意了

protected $fillable = ['nickname', 'email', 'website', 'content', 'article_id']; 

应该是添加到Comment模型中,而不是CommentController控制器。这是一个小坑

不错不错,新手看了很有收获

大作业做完了,嘿嘿,可以进我的工程目录查看

@johnlui 非常感谢您的教程。大作业遇到困难了,请教一下:
评论管理的列表页,怎么才能把所有评论对应的标题都循环显示出来呢?
单独某个评论的标题我知道可以这么调用:
$title = Comment::find(1)->belongsToArticle->title;

@johnlui @weir008
太巧了,今天我也遇到同样的问题,看评论管理列表截图,每个评论有取对应Article的标题,如何在列表页一次性关联获取,这个对于评论和文章应该是belongsToArticle的关系!

另外,我觉得在Commet的model里面写个hasOneArticle,一个查询获得comment和对应Article的数据更符合业务模式。
Model

    public function hasOneArticle()
    {
//      return $this->belongsTo('App\Models\article', 'article_id' , 'id');
        return $this->hasOne('App\Models\article', 'id', 'article_id');
    }

Controller

    public function edit($id)
    {
        return view('admin/comment/edit')->withComment(Comment::with('hasOneArticle')->find($id));
    }

页面可以通过
{{ $comment->hasOneArticle->title }}
来获得单独评论的标题

belongsTo 和 hasOne 搞乱了。。。。

@weir008
没想到被我一通瞎搞弄出来了。。。
有了 hasOneArticle 方法 其实已经离解决不远了。。。

    public function index()
    {
//      return view('admin/comment/index')->withComments(Comment::all());
        return view('admin/comment/index')->withComments(Comment::with('hasOneArticle')->get());
    }

@daweilang 终于搞定了~

谢谢楼主啊,花了两个下午学会了Laravel的皮毛,还是挺开心的~
大作业已做,准备找Laravel模板做自己的东西啦~
希望楼主再接再厉, 出点深入的教程,要不然就只有我们自己摸索啦~

教程nice

看完五个教程了,楼主好样的,现在开始做大作业

分页要怎么写哪?普通的分页写出来了,但是一对多的评论分页就不会写了
Article::with('hasManyComments')->paginate(5)->find($id);
这样写不对啊?求帮助

thanks a lot

show tiitle method :

  • Comment.php
//  "$comment->hasOneArticle->title" call there
    public function hasOneArticle()
    {

        return $this->hasOne('App\Article', 'id', 'article_id');

    }
  • CommentController.php
    public function index()
    {
//  "$comment->hasOneArticle->title" has no call function in  there
        return view('admin/comment/index')->withComments(Comment::all());
        // return view('admin/comment/index')->withComments(Comment::with('hasOneArticle')->get());

    }

  • index.blade.php
$comment->hasOneArticle->title

非常感谢,一个小案例快速上手,用的最新laravel 5.4 文件目录稍有不同,影响不大

教程非常清晰,简单,不愧为最适合**人的教程

给个赞

commented

在最新的版本上学习完成,但是我现在想实现app的api访问,有相关例子吗?不知道如何入手 @johnlui

commented

johnlui ,写的太好了

博主,页面里有错误显示的div,但是validation没讲到,有点不完整了。

对于评论和文章的关联问题,到底是用belongsTo还是hasOne?有点搞混了

@witcherhunter 根据字面意思理解嘛

非常赞

@daweilang 我还是用的belongsTo,只是在循环时,对每个comment Model调用hasOneArticle,然后取出标题,这样是否会效率比较低,多查几次?我想了下,其实还是要1+N次查询才能弄到吧,应该是一样的。。。而且你的这种查法,是不是会忽略并没有归属那篇文章的评论?毕竟也有单纯删除了文章,没有删除相关评论的可能。。。

没有人对这个$request有疑问吗,获取到用var_dump打印浏览器就挂了。。。

基础规划
我们需要新建一个表专门用来存放数据库

多了个

commented

laravel的容器、服务提供是最核心也是最难的部分,研究了两天了,希望可以有教程

你好,我想请教一个问题:
{!! csrf_field() !!} 与 **{{ csrf_field() }}**它们两个有没有区别?

关于ArticleController的show函数:

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

其中里面用了个Article::with('hasManyComments')->find($id)。如果直接写Article->find($id)似乎也没有太大问题。两者有什么区别?

我认为在这个场合可能其实没什么区别。

with这个函数,这个在作者的这篇文章里提到过,是一个叫做预加载的技巧。对应的官方文档在这里
一般是应用在$books = App\Book::with('author')->get();这种场合,这种场合下books是一个对象集合,with能够将

select * from books
select * from authors where id=1
select * from authors where id=2
select * from authors where id=3
...

变成

select * from books
select * from authors where id in (1, 2, 3, 4, 5, ...)

节约了语句的数量。

然而对应于上文Article->find的场合,find每次只返回一个Article,用with来预加载的意义似乎不是很大。

希望各位指正……

非常感谢楼主,楼主好人,给你点赞 。刚接触laravel,学习这个教程之后 对它有了一个大概了解了

用的最新的 5.4版本,突然在做 登出logout的时候 报错,大概就是路由错误的意思 各种百度这才发现 5.4版的路由 logout 已经改为了Post ,所以个人目前5.4版本登出 采用一个form post提交。希望对新手有用

对于新手来说,模板拿出来讲讲也很友好啊。

第一次学laravel,完全是跟着楼主的步骤走,浅显易懂,受益匪浅,谢谢楼主大神,给你点赞

不错,大家一起加油。