Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2016 版 Laravel 系列入门教程(五)【最适合中国人的 Laravel 教程】 #8

Closed
johnlui opened this issue Jun 6, 2016 · 47 comments

Comments

@johnlui
Copy link
Owner

johnlui commented Jun 6, 2016

本教程示例代码见: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 就真正入门啦~~

@mingyun
Copy link

mingyun commented Jun 11, 2016

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

@Lidisam
Copy link

Lidisam commented Jun 11, 2016

{{ old('xxx') }}

@jayleco
Copy link

jayleco commented Jun 12, 2016

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

@MomoFu
Copy link

MomoFu commented Jun 14, 2016

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

@johnlui
Copy link
Owner Author

johnlui commented Jun 14, 2016

@MomoFu 👍 感谢

@codekenq
Copy link

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

@821241614
Copy link

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

@xiaodingchen
Copy link

写的好

@luoziluojun
Copy link

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

@yuyudaedu
Copy link

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

@ucoker
Copy link

ucoker commented Aug 18, 2016

谢谢

@miaoyaoyao
Copy link

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

@h476564406
Copy link

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

@ufoe
Copy link

ufoe commented Sep 27, 2016

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

@ufoe
Copy link

ufoe commented Sep 28, 2016

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

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

@ghost
Copy link

ghost commented Oct 3, 2016

支持一下

@xmpx310
Copy link

xmpx310 commented Nov 1, 2016

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

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

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

@ghost
Copy link

ghost commented Nov 3, 2016

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

@ghost
Copy link

ghost commented Nov 4, 2016

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

@weir008
Copy link

weir008 commented Nov 16, 2016

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

@daweilang
Copy link

daweilang commented Nov 16, 2016

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

@weir008
Copy link

weir008 commented Nov 17, 2016

@daweilang 终于搞定了~

@susucool527237808
Copy link

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

@mingyun
Copy link

mingyun commented Dec 4, 2016

教程nice

@zhangweijin
Copy link

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

@lumoDo
Copy link

lumoDo commented Dec 12, 2016

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

@NominationP
Copy link

thanks a lot

@NominationP
Copy link

NominationP commented Dec 22, 2016

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

@sherri-x
Copy link

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

@eshine361
Copy link

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

给个赞

@cmsly
Copy link

cmsly commented Mar 27, 2017

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

@yanyue251314
Copy link

johnlui ,写的太好了

@kingofswing
Copy link

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

@jiazhen-chou
Copy link

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

@johnlui
Copy link
Owner Author

johnlui commented Apr 17, 2017

@witcherhunter 根据字面意思理解嘛

@ghost
Copy link

ghost commented May 2, 2017

非常赞

@nixizhiguang
Copy link

nixizhiguang commented May 8, 2017

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

@muchao512
Copy link

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

@dchaofei
Copy link

dchaofei commented Aug 8, 2017

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

多了个

@CreationLee
Copy link

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

@dchaofei
Copy link

dchaofei commented Aug 8, 2017

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

@TerryGeng
Copy link

关于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来预加载的意义似乎不是很大。

希望各位指正……

@oyyhouse
Copy link

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

@oyyhouse
Copy link

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

@supenghai
Copy link

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

@silaker
Copy link

silaker commented Sep 27, 2017

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

@johnlui johnlui closed this as completed Nov 11, 2017
@echojack
Copy link

不错,大家一起加油。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests