前奏
在开始正文之前,我们首先来说说在实际的开发中,经常会接触到几种常见的对应关系模式:
One-To-One //一对一
One-To-Many //一对多
Many-To-Many //多对多
不知道你对这些概念是一种什么样的感受,如果是不太理解的。你可以将这些概念应用到生活中,理解起来就很简单了,就举一个与我们在网上经常见到的例子:
User-To-Profile // One-To-One
User-To-Articles // One-To-Many
Article-To-Comments // One-To-Many
Articles-To-Tags // Many-To-Many
翻译过来就是:
一个用户对应一个用户档案
一个用户可以发表多篇文章
一篇文章可以有多个评论
而文章和标签确实多对多的关系,一篇文章可以有多个标签;一个标签可以属于多篇文章
在这些关系模型中,最难实现的就是Many-To-Many
这种多对多的关系,但是我们这个简单地博客并没有用户管理,也就是并没有开放让用户注册,所以我们在这里还是要挑战一下难度,实现Articles-To-Tags
这种Many-To-Many
关系,借助Laravel的强大的Eloquent,实现这个功能还是比较顺心的。至于一对一和一对多这两种关系,可以触类旁通。
创建tags表
要实现Articles-To-Tags
这种Many-To-Many
关系,我们需要tags
表和Tag
模型,所以我们分别来创建之。
php artisan make:migration create_tags_table --create=tags
打开生成的migration
文件,为up()
方法增加一行代码:
public function up()
{
Schema::create('tags', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->timestamps();
});
}
这里我们增加了$table->string('name');
这一行,这个字段表示为tags table
添加一个name
字段,代表标签的名字。
接下来,我们为tags
表创建一个Tag
模型:
php artisan make:model Tag
生成了Tag
模型之后,我们先不用去管Tag.php
文件,因为我们还需要一张关系表article_tag
,这个表只存tag_id
和article_id
,所以我们来创建之:
php artisan make:migration create_article_tag_table --create=article_tag
打开migration文件来为之加上tag_id
和article_id
这两个字段:
public function up()
{
Schema::create('article_tag', function (Blueprint $table) {
$table->increments('id');
$table->integer('article_id')->unsigned()->index();
$table->foreign('article_id')->references('id')->on('articles')->onDelete('cascade');
$table->integer('tag_id')->unsigned()->index();
$table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade');
$table->timestamps();
});
}
这里貌似就添加tag_id
和article_id
这两个字段,但是用了很多行代码,我们只要是理解下面这个:
$table->foreign('article_id')->references('id')->on('articles')->onDelete('cascade');
foreign()
:外键references()
:参照字段on()
:参照表onDelete()
:删除时的执行动作这里是跟着删除,比如删除了某篇文章,我们将article_tag中包含article_id一样的记录也删除
最后,执行migration生成article_tag
表:
php artisan migrate
OK,生成这两个表之后,我们就可以正式开始我们的工作了。
声明Eloquent的关系
Articles 和 Tags 是多对多的关系,所以我们需要在Article.php
中声明下面的关系:
public function tags()
{
return $this->belongsToMany('App\Tag');
}
在Tag.php
,也同样:
public function articles()
{
return $this->belongsToMany('App\Article');
}
我们使用$this->belongsToMany()
来表明Eloquent的关系,这里需要注意的是如果你的外键并不是article_id
和tag_id
,你需要在第三个参数进行设置,写成类似下面这样:
public function articles()
{
return $this->belongsToMany('App\Article','conversation_id');
}
OK,这样,我们的多对多关系就声明完毕了。
使用Select2
在开始之前,我们使用tinker
生成几个tag
,过程就不演示了,最后是这样的:
然后,为了更好地用户体验,我们引入Select2,这个对选择多个选项的时候表现得异常完美。
Select2 用法:https://select2.github.io/examples.html
我们在app.blade.php
引入Select2的css
文件和js
文件:
<link rel='stylesheet' href="/css/select2.css" type='text/css' media='all'/>
<script src="/js/jquery-2.1.0.min.js"></script>
<script src="/js/select2.full.min.js"></script>
在<head></head>
标签内,我们还引入了jquery,因为select2依赖于jquery,所以。注意文件的下载或来源,请自行获取。
引入之后,我们就可以在文件创建的页面依旧使用我们的Form
来生成我们的选择框了,来到
articles/create.blade.php
文件,在published_at
下面添加一个输入表单:
<div class="form-group">
{!! Form::label('tag_list','选择标签') !!}
{!! Form::select('tag_list[]',$tags,null,['class'=>'form-control js-example-basic-multiple','multiple'=>'multiple']) !!}
</div>
这里需要注意的是tag_list[]
,如果我们只是使用tag_list
,就只能选到一个标签,如果我们需要选择多个,我们需要已数组的形式来储存我们的标签,还有一个就是指定一下'multiple'=>'multiple'
,就是开启支持多选模式。然后$tags
就是我们需要从数据库获tags表取到得数据,所以自然而然,我们到ArticleController
中的create()
方法中,稍微修改一下代码:
public function create()
{
$tags = Tag::lists('name', 'id');
//为了在界面中显示标签name,id为了在保存文章的时候使用。
return view('articles.create',compact('tags'));
}
这里我们使用lists()
方法将Tag
中(tags数据表)name
和id
以一个Eluqoent的方式返回,你可以使用dd($tags)
,来看看。恩,这个时候来看看我们的create
页面:
这时候我们发现,样式并没有Select2
那么好看,那是因为我们还没有初始化Select2
,所以我们在create.blade.php
写几行简单地js代码:
<script type="text/javascript">
$(function() {
$(".js-example-basic-multiple").select2({
placeholder: "添加标签"
});
});
</script>
在@endsection
紧接着的上一行加上上面的代码,这里我们使用jquery的选择器,然后调用select2();
来初始化我们的选择框,再来看看效果:
很完美,我们将整个UI完善得还不错,我们用dd()
;来看看我们表单提交过来的是什么,在ArticleController
中的store()
方法中添加一行代码:
dd($request->all());
我们来看看效果:
我们看到得tag_list
是一个数组,里面的值并不是我们选择的标签的name
,而是标签的id
,这样我们就可以使用laravel提供的attach()
来添加我们的标签了,这个attach()
接受一个id
的数组,这里正好!,所以我们来稍微来修改一下store()
方法:
public function store(Requests\StoreArticleRequest $request)
{
$input = $request->all();
$input['intro'] = mb_substr($request->get('content'),0,64);
$article = Article::create($input);
$article->tags()->attach($request->input('tag_list'));
return redirect('/');
}
我们这里首先将Article::create($input)
赋予$article
变量(Eloquent对象),然后使用$article->tags()->attach()
来添加标签,并将我们的标签数组传给attach()
方法,我们来看看有没有成功:
这里的文章是发表成功了,我们再来看看我们的标签是否添加成功,来看看我们的article_tag
表:
是添加了三个标签,但是我们发现这个created_at
和updated_at
貌似有点问题,我们来修复一下,在Article.php
中的tags()
方法中:
public function tags()
{
return $this->belongsToMany('App\Tag')->withTimestamps();
}
我们在后面直接使用withTimestamps()
来同步我们的时间,我们再来试一试:
再来看看我们的数据库:
看到最后的两个记录,很完美。
在视图中显示我们的tags
我们既然有了标签,我们为什么不来将它展示出来呢?在articles/index.blade.php
中,我们来将文件的标签输出一下:
<h2 class="post-title pad">
<a href="/articles/{{ $article->id }}"> {{ $article->title }}</a>
</h2>
<ul class="post-meta pad group">
<li><i class="fa fa-clock-o"></i>{{ $article->published_at->diffForHumans() }}</li>
@if($article->tags)
@foreach($article->tags as $tag)
<li><i class="fa fa-tag"></i>{{ $tag->name }}</li>
@endforeach
@endif
</ul>
我们在<h2>
标签下面增加一个<ul>
列表,然后是首先将发表日期published_at
输出了,这里我们使用了Carbon的diffForHumans()
方法,这个方法就会产生几分钟之前,几个小时之前的效果,这里也可以体会我们之前需要将published_at
这个对象作为Carbon对象来对待了,如果是简单地字符串,是不能调用Carbon的diffForHumans()
方法的。
接下来,我们使用$article->tags
取得文章的标签,这个tags
就是我们声明多对多关系的tags()
方法。我们来看看效果:
我们发现我们的多少分钟之前
都是英文,那是因为我们没有设置Carbon,我们来修复一下,在app/Providers/AppServiceProvider.php
中的boot()
方法添加下面这一行:
\Carbon\Carbon::setLocale('zh');
然后刷新,见证一下奇迹吧:
总结
到这里我们利用laravel提供的attach()
方法将基本的多对多关系实现了,并且还稍微美化了一下输出,将published_at
字段完美呈现。接下来我打算说一说怎么实现修改文章了,这是一个必走的流程嘛。所以。。。
最后,Happy Hacking