一步一步用laravel开发回风博客系统-07-标签的增删改查

原作者网站:http://laravelcoding.com/blog?tag=L5+Beauty
可以参考中文站点:http://laravelacademy.org/resources/blog

我是基于5.2的,而且有些东西我觉得有必要有的没必要,所以思路是跟着以上两个参考着搞,具体还是有区别的,我最终代码放在了Github:https://github.com/wedojava/hfblog.dev

由于正在补英语,所以有些我能看懂的就没从原作者那里翻译。

上节,我们实现了后台的路由,Post页面和控制器,这节,我们实现在后台页的Tag增删改查

创建标签模型和迁移

(1).创建 Tag 模型和迁移

php artisan make:model --migration Tag

该命令会在 app 目录下创建模型文件 Tag.php,由于我们在 make:model 命令中使用了 --migration 选项,所以同时会创建 Tag 模型对应的数据表迁移。

(2).在标签(Tag)和文章(Post)之间存在多对多的关联关系,因此还要按照下面的命令创建存放文章和标签对应关系的数据表迁移:

php artisan make:migration --create=post_tag_pivot create_post_tag_pivot

1、编辑标签迁移文件

database/migrations 目录下编辑新创建的标签迁移文件内容如下:

<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateTagsTable extends Migration
{

/**
* Run the migrations.
*
* @return void
*/

public function up()
{

Schema::create('tags', function (Blueprint $table) {
$table->increments('id');
$table->string('tag')->unique();
$table->string('title');
$table->string('subtitle');
$table->string('page_image');
$table->string('meta_description');
$table->string('layout')->default('blog.layouts.index');
$table->boolean('reverse_directions');
$table->timestamps();
});
}

/**
* Reverse the migrations.
*
* @return void
*/

public function down()
{

Schema::drop('tags');
}
}

下面我们对上面迁移文件中新增的字段作简要说明:

  • page_image:标签图片
  • meta_description:标签介绍
  • layout:博客终归要使用布局
  • reverse_directions:在文章列表按时间升序排列博客文章(默认是降序)

2、编辑文章与标签对应关系迁移

编辑文章与标签对应关系表迁移文件内容如下:

<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreatePostTagPivot extends Migration
{

/**
* Run the migrations.
*
* @return void
*/

public function up()
{

Schema::create('post_tag_pivot', function (Blueprint $table) {
$table->increments('id');
$table->integer('post_id')->unsigned()->index();
$table->integer('tag_id')->unsigned()->index();
$table->timestamps();
});
}

/**
* Reverse the migrations.
*
* @return void
*/

public function down()
{

Schema::drop('post_tag_pivot');
}
}

3、运行迁移

登录到 Homestead 虚拟机在项目根目录下通过运行如下 Artisan 命令以生成这两个数据表:

php artisan migrate

实现 admin.tag.index

上节中,我们已经创建了 TagController 并定义了相应的路由。要实现路由 admin.tag.index 只需要更新 TagController 的 index 方法,然后为其提供一个渲染视图即可。

实现 TagController@index

修改TagControllerindex方法:

public function index()
{
$tags = Tag::all();
return view('admin.tag.index')->withTags($tags);
}

并添加 use App\Tag; 引用。

创建 admin.tag.index 视图 resources/views/admin/tag/index.blade.php

@extends('admin.layout')

@@section('content')
<div class="container-fluid">
<div class="row page-title-row">
<div class="col-md-6">
<h3>Tags <small>» Listing</small></h3>
</div>
<div class="col-md-6 text-right">
<a href="/admin/tag/create" class="btn btn-success btn-md">
<i class="fa fa-plus-circle"></i> New Tag
</a>
</div>
</div>

<div class="row">
<div class="col-sm-12">

@include('admin.partials.errors')
@include('admin.partials.success')

<table id="tags-table" class="table table-striped table-bordered">
<thead>
<tr>
<th>Tag</th>
<th>Title</th>
<th class="hidden-sm">Subtitle</th>
<th class="hidden-md">Page Image</th>
<th class="hidden-md">Meta Description</th>
<th class="hidden-md">Layout</th>
<th class="hidden-sm">Direction</th>
<th data-sortable="false">Actions</th>
</tr>
</thead>

<tbody>
@foreach ($tags as $tag)
<tr>
<td>{{ $tag->tag }}</td>
<td>{{ $tag->title }}</td>
<td class="hidden-sm">{{ $tag->subtitle }}</td>
<td class="hidden-md">{{ $tag->page_image }}</td>
<td class="hidden-md">{{ $tag->meta_description }}</td>
<td class="hidden-md">{{ $tag->layout }}</td>
<td class="hidden-sm">
@if ($tag->reverse_directions)
Reverse
@else
Normal
@endif
</td>
<td>
<a href="/admin/tag/{{ $tag->id }}/edit" class="btn btn-xs btn-info">
<i class="fa fa-edit"></i> Edit
</a>
</td>
</tr>
@endforeach
</tbody>

</table>
</div>
</div>
</div>
@stop

@section('scripts')
<script>
$(function(){
$('#tag-table').DataTable({});
});
</script>

@stop

这个模板很简单,我们使用了后台布局,在文件顶部是页面标题和创建新标签的按钮,然后用表格来显示所有标签,最后,在页面顶部我们引入了一些 JavaScript 脚本将表格转化为 DataTables。

试着预览下tag页,一切正常,除了表格里没有任何内容。我们现在实现创建tag。

实现 admin.tag.create

接下来我们将会实现添加标签功能,这样当你点击“添加标签”按钮时就会跳转到填写标签表单页面。

1、实现 TagController@create

修改 app/Http/Controllers/Admin/TagController.phpcreate 方法如下:

// 在 TagController 控制器顶部添加 $fields 属性
protected $fields = [
'tag' => '',
'title' => '',
'subtitle' => '',
'meta_description' => '',
'page_image' => '',
'layout' => 'blog.layouts.index',
'reverse_directions' => 0,
];

// 替换 create() 方法如下
/**
* Show form for creating new tag
*/
public function create()
{
$data = [];
foreach ($this->fields as $field => $default) {
$data[$field] = old($field, $default);
}

return view('admin.tag.create', $data);
}

该方法会在点击“添加新标签”或者表单被填充但是验证失败时执行,对于后者我们会将传过来的数据通过 old 方法回写到表单中。

2、创建 admin.tag.create 视图

创建 resources/views/admin/tag/create.blade.php 文件:

@extends('admin.layout')

@section('content')
<div class="container">
<div class="row page-title-row">
<div class="col-md-12">
<h3>Tags <small>» Create New Tag</small></h3>
</div>
</div>

<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">New Tag Form</h3>
</div>
<div class="panel-body">

@include('admin.partials.errors')

<form class="form-horizontal" role="form" method="POST" action="/admin/tag">
<input type="hidden" name="_token" value="{{ csrf_token() }}">

<div class="form-group">
<label for="tag" class="col-md-3 control-label">Tag</label>
<div class="col-md-3">
<input type="text" class="form-control" name="tag" id="tag" value="{{ $tag }}" autofocus>
</div>
</div>

@include('admin.tag._form')

<div class="form-group">
<div class="col-md-7 col-md-offset-3">
<button type="submit" class="btn btn-primary btn-md">
<i class="fa fa-plus-circle"></i>
Add New Tag
</button>
</div>
</div>

</form>

</div>
</div>
</div>
</div>
</div>

@stop

由于我们会在添加和编辑标签时共用同一个表单,所以我们将表单部分放到一个独立的局部视图 admin.tag._form 中,下面我们就来创建这个局部视图:

3、创建 admin.tag._form 局部视图

<div class="form-group">
<label for="title" class="col-md-3 control-label">
Title
</label>
<div class="col-md-8">
<input type="text" class="form-control" name="title" id="title" value="{{ $title }}">
</div>
</div>

<div class="form-group">
<label for="subtitle" class="col-md-3 control-label">
Subtitle
</label>
<div class="col-md-8">
<input type="text" class="form-control" name="subtitle" id="subtitle" value="{{ $subtitle }}">
</div>
</div>

<div class="form-group">
<label for="meta_description" class="col-md-3 control-label">
Meta Description
</label>
<div class="col-md-8">
<textarea class="form-control" id="meta_description" name="meta_description" rows="3">{{ $meta_description }}</textarea>
</div>
</div>

<div class="form-group">
<label for="page_image" class="col-md-3 control-label">
Page Image
</label>
<div class="col-md-8">
<input type="text" class="form-control" name="page_image" id="page_image" value="{{ $page_image }}">
</div>
</div>

<div class="form-group">
<label for="layout" class="col-md-3 control-label">
Layout
</label>
<div class="col-md-4">
<input type="text" class="form-control" name="layout" id="layout" value="{{ $layout }}">
</div>
</div>

<div class="form-group">
<label for="reverse_directions" class="col-md-3 control-label">
Direction
</label>
<div class="col-md-7">
<label class="radio-inline">
<input type="radio" name="reverse_directions" id="reverse_directions"
@if (! $reverse_directions)
checked="checked"
@endif
value="0">
Normal
</label>
<label class="radio-inline">
<input type="radio" name="reverse_directions"
@if ($reverse_directions)
checked="checked"
@endif
value="1">
Reversed
</label>
</div>
</div>

实现 admin.tag.store

现在我们已经有了创建标签表单,还需要编写表单被提交后保存标签的业务逻辑代码。

1、创建表单请求类 TagCreateRequest

Laravel 5.2 的一个优秀特性就是表单请求类,这些类可以对指定表单字段进行验证。

使用如下 Artisan 命令创建一个新的 TagCreateRequest:

php artisan make:request TagCreateRequest

编辑刚创建的 app/Http/Requests/TagCreateRequest.php

<?php

namespace App\Http\Requests;

use App\Http\Requests\Request;

class TagCreateRequest extends Request
{

/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/

public function authorize()
{

return true;
}

/**
* Get the validation rules that apply to the request.
*
* @return array
*/

public function rules()
{

return [
'tag' => 'required|unique:tags,tag',
'title' => 'required',
'subtitle' => 'required',
'layout' => 'required',
];
}
}

该类继承自 Illuminate\Foundation\Http\FormRequest,当该类实例化的时候就会对请求进行验证,需要验证的项目有两个:

  • authorize() :验证用户是否经过登录认证
  • rules():返回验证规则数组

表单请求的神奇之处在于会在表单请求类实例化的时候对请求进行验证,如果验证失败,会直接返回表单提交页面并显示错误信息。这意味着如果将表单请求作为控制器方法参数,那么验证工作将会在执行对应方法第一行代码之前进行。接下来我们就来定义保存的控制器方法。

2、实现 TagController@store

编辑控制器 TagController.php 代码如下:

<?php

// 在TagController顶部添加这个use语句
use App\Http\Requests\TagCreateRequest;

// 修改 store() 方法代码如下
/**
* Store the newly created tag in the database.
*
* @param TagCreateRequest $request
* @return Response
*/
public function store(TagCreateRequest $request)
{
$tag = new Tag();
foreach (array_keys($this->fields) as $field) {
$tag->$field = $request->get($field);
}
$tag->save();

return redirect('/admin/tag')
->withSuccess("The tag '$tag->tag' was created.");
}

现在,通过依赖注入, TagCreateRequest 被构造、表单被验证,只有验证通过后才会将请求参数传递到 store 方法。 store 方法接下来才会创建并保存新的 Tag 实例。最后,页面重定向到后台标签列表,并带上保存成功消息。

实现 admin.tag.edit

接下来,我们来编辑路由方法 admin.tag.edit 来显示标签编辑表单。

1、实现 TagController@edit

更新 TagController 类,编辑 edit 方法如下:

public function edit($id)
{
$tag = Tag::findOrFail($id);
$data = ['id' => $id];
foreach (array_keys($this->fields) as $field) {
$data[$field] = old($field, $tag->$field);
}
return view('admin.tag.edit', $data);
}

2、创建 admin.tag.edit 视图

创建 resources/views/admin/tag/edit.blade.php

@extends('admin.layout')

@section('content')
<div class="container">
<div class="row page-title-row">
<div class="col-md-12">
<h3>Tags <small>» Edit Tag</small></h3>
</div>
</div>

<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Tag Edit Form</h3>
</div>
<div class="panel-body">

@include('admin.partials.errors')
@include('admin.partials.success')

<form class="form-horizontal" role="form" method="POST" action="/admin/tag/{{ $id }}">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
<input type="hidden" name="_method" value="PUT">
<input type="hidden" name="id" value="{{ $id }}">

<div class="form-group">
<label for="tag" class="col-md-3 control-label">Tag</label>
<div class="col-md-3">
<p class="form-control-static">{{ $tag }}</p>
</div>
</div>

@include('admin.tag._form')

<div class="form-group">
<div class="col-md-7 col-md-offset-3">
<button type="submit" class="btn btn-primary btn-md">
<i class="fa fa-save"></i>
Save Changes
</button>
<button type="button" class="btn btn-danger btn-md" data-toggle="modal" data-target="#modal-delete">
<i class="fa fa-times-circle"></i>
Delete
</button>

</div>
</div>

</form>

</div>
</div>
</div>
</div>
</div>

{{-- 确认删除 --}}
<div class="modal fade" id="modal-delete" tabIndex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">
×
</button>
<h4 class="modal-title">Please Confirm</h4>
</div>
<div class="modal-body">
<p class="lead">
<i class="fa fa-question-circle fa-lg"></i>
Are you sure you want to delete this tag?
</p>
</div>
<div class="modal-footer">
<form method="POST" action="/admin/tag/{{ $id }}">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
<input type="hidden" name="_method" value="DELETE">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="submit" class="btn btn-danger">
<i class="fa fa-times-circle"></i> Yes
</button>
</form>
</div>
</div>
</div>
</div>

@stop

测试下,编辑什么都应该除了具体功能,界面都OK了。下面我们来做编辑和删除的逻辑。

实现 admin.tag.update

admin.tag.store 路由处理 admin.tag.create 表单一样,我们使用 admin.tag.update 路由处理 admin.tag.edit 表单。

1、创建表单请求类 TagUpdateRequest

php artisan make:request TagUpdateRequest

编辑刚刚生成的 TagUpdateRequest.php :

<?php

namespace App\Http\Requests;

use App\Http\Requests\Request;

class TagUpdateRequest extends Request
{

/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/

public function authorize()
{

return true;
}

/**
* Get the validation rules that apply to the request.
*
* @return array
*/

public function rules()
{

return [
'title' => 'required',
'subtitle' => 'required',
'layout' => 'required',
];
}
}

2、实现 TagController@update

我们修改 TagControllerupdate 方法为:

// 在TagController顶部添加新的use语句
use App\Http\Requests\TagUpdateRequest;

// 替换 update() 方法如下

public function update(TagUpdateRequest $request, $id)
{
$tag = Tag::findOrFail($id);

foreach (array_keys(array_except($this->fields, ['tag'])) as $field) {
$tag->$field = $request->get($field);
}

$tag->save();

return redirect("/admin/tag/$id/edit")
->withSuccess("Change Saved.");
}

测试下,应该可以修改标签内容了。

实现标签删除功能

实现 TagController@destroy

要在编辑标签页面实现删除标签功能,首先要修改 TagController.phpdestroy 方法如下:

public function destroy($id)
{
$tag = Tag::findOrFail($id);
$tag->delete();

return redirect('/admin/tag')
->withSuccess("The '$tag->tag' tag has been deleted.");
}

我们注意到,每次要删除一条信息必须要点进去才能删除,这样比较麻烦,如果在列表就能有删除按钮岂不是更好?下面我们来实现。

实现列表删除按钮

删除tag的方法我们已经有了,调用这个方法需要 data-method = "delete",更关键的是,需要有crsf_token
修改admin.tag.index视图为:

<a href="/admin/tag/{{ $tag->id }}/edit" class="btn btn-xs btn-info">
<i class="fa fa-edit"></i> Edit
</a>
<form action="/admin/tag/{{ $tag->id }}" method="POST" style="display: inline;">
<input name="_method" type="hidden" value="DELETE">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-xs btn-danger" data-confirm="Are you sure?"><i class="fa fa-times-circle"></i> Del</button>
</form>

这样就可以在列表页面删除内容了,下面,我们换种方法来实现这个按钮,这要借助于脚本了:
JeffreyWay/laravel.js:这个已经不适用laravel 5.2——change ‘name’: ‘csrf_token’, to ‘name’: ‘_token’,但是这个里面的评论多些,是此脚本的原作。

下面两个解决了laravel 5以后的问题,可以在laravel 5.2里适用。适用方式在脚本里面有说明。

roNn23/laravel.js forked from JeffreyWay/laravel.js:13 star,

soufianeEL/laravel.jsforked from JeffreyWay/laravel.js:26 star。

当引入上面的脚本后:

<a href="/admin/tag/{{ $tag->id }}/edit" class="btn btn-xs btn-info">
<i class="fa fa-edit"></i> Edit
</a>
<a href="/admin/tag/{{ $tag->id }}" class="btn btn-xs btn-danger" data-method="delete" data-token="{{csrf_token()}}" data-confirm="Are you sure?">
<i class="fa fa-times-circle"></i> Del
</a>

http://laraveldaily.com/crud-how-to-avoid-building-whole-form-for-delete-button/
更好的办法:通过js实现

移除 admin.tag.show

你可能已经注意到还有一个路由到目前为止还没有被使用,admin.tag.show 用来显示标签详情,该路由通常用于给那些没有编辑权限的用户查看详情。

在本系统中我们不需要查看标签详情(实际上这个功能也没有什么意义),所以将其删除。

首先在 app/Http/routes.php 中修改路由如下:

// 将如下这行代码
resource('admin/tag', 'TagController');
// 修改成
resource('admin/tag', 'TagController', ['except' => 'show']);

然后在控制器 TagController.php 中移除 show() 方法。