如何用Laravel在30分钟内快速搭建一个博客系统?

2026-05-03 00:303阅读0评论SEO教程
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计4970个文字,预计阅读时间需要20分钟。

如何用Laravel在30分钟内快速搭建一个博客系统?

相关专题内容,请直接提供,避免试图解释、举例或使用数字。

  • 有一个命令可以看到当前所有生效的路由 php artisan route:list ,控制台自动弹出的表格中,Method 表示 请求方法,即
    Route::这里的方法()  ,URI 表示 请求地址,即 Route::action('这里就是uri'), name 表示 路由别名,可以通过 Route::action('uri', 'Controller@function')->name('这里可以定义路由别名')

  • 你会发现,有7条关于 blog 的路由,这就是 Route::resource('blog', 'BlogController') 帮我们生成的。(5个功能7条是因为 添加和编辑多了2条载入视图的路由)

  • 完成增删改查吧: 首先完成 BlogController@index : 展示列表

    • 先来个入口链接,打开 home.blade.php

    {{-- 上面说过这里会添加一个按钮 --}} <a href="{{ route('blog.index') }}" class="btn btn-lg btn-block btn-primary">点击这里查看我的博客</a>

    • 编辑 BlogController@index => 这里还是再提醒一下吧,这是说 BlogController 的 index() 方法。控制器文件都在 app\Http\Controllers 中

    <?php namespace App\Http\Controllers; use App\Blog; //这里是使用命令创建控制器时,通过 --model=Blog 自动帮我们生成的 use Illuminate\Http\Request; class BlogController extends Controller{ public function index() { // 查询数据,并且让查询结果是一个可分页对象 $blogs = Blog::orderBy('created_at', 'desc') // 调用 Blog模型 的静态方法 orderBy('根据created_at字段', '倒叙排序') ->paginate(6); // -> 链式操作:paginate(6) 即数据没页6条 // 跳转到视图并传值 return view('blog.index', [ //第一个参数是说,视图模板是 /resources/views/blog/index.blade.php 'blogs' => $blogs, //这里是说,我们给视图传递一个叫 $'blogs'的变量,值是前面我们查询的数据,也叫$blogs。 ]); // view() 的第二参数也可以使用 view(..., compact('blogs')) }

    • 此时刷新页面当然会报错了,因为我们的视图还不存在,新建文件夹 /resources/views/blog/index.blade.php

    @extends('layouts.app') @section('content') <div class="container"> <div class="row justify-content-center"> <div class="card"> <div class="card-header">这是页面小标题</div> <div class="card-body"> 这里是内容 </div> </div> </div> </div> @endsection

    • 完成视图里面的内容

    <div class="container"> <div class="row justify-content-center"> <div class="card"> <div class="card-header">文章列表</div> <div class="card-body"> <table class="table table-hover table-bordered"> <thead class="bg-info"> <tr> <th>文章标题</th> <th>发布时间</th> <th>相关操作</th> </tr> </thead> <tbody> {{-- 这里通过 @foreach 遍历数据 --}} @foreach ($blogs as $blog) <tr> <td>{{ $blog->title }}</td> <td>{{ $blog->created_at }}</td> <td></td> </tr> @endforeach </tbody> <tfoot> {{-- 这里通过 $blogs->links() 来显示分页按钮 --}} {{ $blogs->links() }} </tfoot> </table> </div> </div> </div> </div>

    • 完成文章添加 添加入口链接, ../layouts/app.blade.php

    {{-- route('路由别名') 在视图上就是一个指向 BlogController@create 的链接 --}} <a href="{{ route('blog.create') }}" class="dropdown-item"> 添加文章 </a>

    • 完成文章的添加 BlogController@create

    public function create(){ return view('blog.create'); //载入视图 }

    • 编辑视图 重点:表单中添加@csrf告诉框架,这是我们自己的表单,不用担心csrf跨站请求伪造的攻击

    @extends('layouts.app') @section('content') <div class="container"> <div class="row justify-content-center"> <div class="card"> <div class="card-header">添加文章</div> <div class="card-body"> {{-- from.method="POST" action="通过 route()函数读取路由别名 " --}} <form method="POST" action="{{ route('blog.store') }}"> {{-- 声明 csrf 令牌 --}} @csrf <div class="form-group"> <label for="title">文章标题</label> <input type="text" class="form-control" id="title" placeholder="请输入文章标题" name="title"> </div> <div class="form-group"> <label for="content">文章内容</label> <textarea id="content" cols="30" rows="10" class="form-control" name="content"></textarea> </div> <button class="btn btn-primary" type="submit">发布新文章</button> </form> </div> </div> </div> </div> @endsection

    • 编辑 BlogController@store

    public function store(Request $request) //这里的 $request 是通过依赖注入的方法实例化的 Request 类的对象,包含的有所有请求的信息 { // 我们只需要调用 Blog模型 的静态方法 create() 插入 $request->post() 数据即可 $blog = Blog::create($request->post()); //改方法的返回值是新插入的数据生成的对象 // redirect() 页面重定向 return redirect()->route('blog.show', $blog); // 这里我们将 $blog 作为参数请求 BlogController@show }

    • 回到页面,点击提交,会发现报错了,Laravel是一个极其注重安全的框架,用户能修改哪些字段,必须要在模型文件中声明,因此打开 app\Blog.php 模型文件

    // 可填字段白名单 protected $fillable = [ 'title', 'content' ];

    • 再次提交,页面一片空白,是因为我们的 BlogController@show 方法还没有写,不过你可以注意到地址栏已经发生了改变。

    • 完成 show 方法

    public function show(Blog $blog) //这里已经通过依赖注入的形式帮我们实例化了 $blog { return view('blog.show', [ 'blog' => $blog, //直接将$blog传给视图进行渲染 ]); }

    • 新建 ../blog/show.blade.php

    @extends('layouts.app') @section('content') <div class="container"> <div class="row justify-content-center"> <div class="card"> <div class="card-header">文章详情</div> <div class="card-body"> <h1 class="text-center">{{ $blog->title }}</h1> <p>发布时间<small>{{ $blog->created_at }}</small></p> <hr> <p> {{ $blog->content }} </p> </div> </div> </div> </div> @endsection

    • 刷新页面,文章就显示出来了。

    • 完成我们的编辑入口链接: 在  ../blog/index.blade.php & show.blade.php 中合理的位置添加一个编辑按钮

    <a href="{{ route('blog.edit', $blog->id) }}" class="btn btn-info">编辑文章</a>

    • 完成 BlogController@edit

    public function edit(Blog $blog){ return view('blog.edit', [ 'blog' => $blog, ]); }

    • 完成视图 重点:action声明文章编号,根据路由要求action在表单中使用@method伪造请求动作类型

    @extends('layouts.app') @section('content') <div class="container"> <div class="row justify-content-center"> <div class="card"> <div class="card-header">编辑文章</div> <div class="card-body"> {{-- action需要声明当前编辑的文章编号$blog->id --}} <form method="POST" action="{{ route('blog.update', $blog->id) }}"> {{-- 声明 csrf 令牌 --}} @csrf {{-- 伪造 PATCH 方法 --}} @method("PATCH") <div class="form-group"> <label for="title">文章标题</label> <input type="text" class="form-control" id="title" placeholder="请输入文章标题" name="title" value="{{ $blog->title }}"> </div> <div class="form-group"> <label for="content">文章内容</label> <textarea id="content" cols="30" rows="10" class="form-control" name="content">{{ $blog->content }}</textarea> </div> <p>发表于<small>{{ $blog->created_at }}</small></p> <p>修改于<small>{{ $blog->updated_at }}</small></p> <button class="btn btn-primary" type="submit">确认编辑</button> </form> </div> </div> </div> </div> @endsection

    • 完成 BlogController@update

    public function update(Request $request, Blog $blog){ $blog->update($request->post()); //调用 $blog对象->update(更新数据组成的数组) 更新 return redirect()->route('blog.show', $blog); }

    • 完成删除功能 在 index.blade.php 和 show.blade.php 合理的位置插入删除按钮

    <a href="javascript:deleteConfirm({{ $blog->id }});" class="btn btn-danger btn-sm">删除文章</a> {{-- 因为删除也需要 csrf 令牌认证,所以弄个表单 --}} <form method="POST" action="{{ route('blog.destroy', $blog->id) }}" id="delete-blog-{{ $blog->id }}"> @csrf {{-- 这里伪造DELETE请求 --}} @method("DELETE") </form>

    • “删除文章” 按钮其实是调用了一个 js 函数,我们在 ../layousts/app.blade.php 中完成

    <script> function deleteConfirm(id) { var confirm = window.confirm('确认要删除这篇文章吗?'); if(confirm === true) { $("#delete-blog-" + id).submit(); //提交表单 }else { window.alert('你选择不删除!'); } } </script>

    • 完成 BlogController@delete 方法

    public function destroy(Blog $blog){ $blog->delete(); return redirect()->route('blog.index'); //跳转到首页 }

    • 增删改查完成。
  • 完善和优化

    • 首先无论增删改查操作,成功后我们没有任何提示,我们使用 session 闪存方法消息吧:

      • 新建组件视图文件夹 /resources/views/components/
      • 然后新建一个组件视图 _message.blade.php => 组件视图我们都用_下划线开头

      <div class="container"> {{-- 遍历 success danger 这两个我们等会会在 session->flash() 方法中设置的"key" --}} @foreach (['success', 'danger'] as $msg) {{-- 当key存在的时候,证明我们给 session flash 闪存里面装载了一次提示信息,那么就显示提示信息 --}} @if (session()->has($msg)) <div class="alert alert-{{ $msg }}"> <ul> <li>{{ session()->get($msg) }}</li> </ul> </div> @endif @endforeach </div>

      • 在 ../layousts/app.blade.php 中导入该组件 重点:@include 导入html片段

      {{-- 在导航下面,内容上面导入 --}}@include('components._message')

      • 编辑 BlogController 里的各种方法,在执行成功某些方法时,页面重定向前,装载闪存。以删除举例

      $blog->delete(); session()->flash('success', '删除文章成功!'); //装载session闪存 return redirect()->route('blog.index');

    • 然后有个问题,就是在于,我们这是一个个人博客,所以只有我们自己可以对博客文章进行增删改,而用户只可以进行查看。因此我们需要:

      • 使用构造函数调用 auth中间件 来排除没有登陆的用户查看文章详情: 编辑 BlogController

      public function __construct(){ $this->middleware('auth')->except('index'); }

      • 在 新增create、编辑edit、和删除方法中加入一次用户认证,以 create 方法举例

      // 因为比较简单,所以我们不用Policy进行认证,我会在以后的教程里面教大家如何使用Policy策略进行权限认证 // 这里我们就使用判断当前用户在数据表中信息的主键id是不是1即可(因为我们在Seeder里面把编号为1的用户设置为了可用的管理员账号) // 1、在代码开头引用 Auth // 2、在方法内先判断一下是不是 1号用户 if(Auth::user()->id != 1) { // Auth::user() 获取当前用户信息 -> id获取属性id(主键) session()->flash('danger', '抱歉,只有博主才可以新增文章!'); return redirect()->back(); }

  • 针对博客的增删改查在这里就结束了。

  • 第三阶段总结

    • 我们使用命令创建了一个 “资源控制器”
    • 我们在 /routes/web.php 定义了一条资源路由
    • 我们使用 BlogController 中的7个方法完成了对 博客文章 的 CURD(增删改查)操作。
    • 我们优化了一下体验,使用 session()->flash() 装载闪存信息,用一个组件html片段加载信息,最后用@include()在模板上加载这个html组件。
    • 我们最后增加了一个简单的权限认证,判断进行增删改的用户是不是管理员,不是管理员则不允许操作,直接装载一条错误提示闪存,然后返回。

    第四阶段 评论功能

    • 新建一个评论资源控制器 php artisan make:controller CommentController --model=Commment
    • 新增一条资源路由,但只支持发表 /routes/web.php Route::resource('comment', 'CommentController', ['only' => 'store']);
    • 在文章详情页面下方增加一个表单 show.blade.php

    <form method="POST" action="{{ route('comment.store') }}"> @csrf <input type="hidden" name="user_id" value="{{ Auth::user()->id }}"> <input type="hidden" name="blog_id" value="{{ $blog->id }}"> <div class="form-group"> <label for="content"></label> <textarea id="content" class="form-control" cols="30" rows="10" name="content">您对这篇文章有什么看法呢?</textarea> </div> <button class="btn btn-primary" type="submit">发表评论</button> </form>

    • 编辑 CommentController@store

    public function store(Request $request){ Comment::create($request->post()); session()->flash('success', '评论成功!'); return redirect()->back(); }

    • 提交评论出错了,又忘了写可填字段白名单,编辑模型 app\Comment

    protected $fillable = [ 'content', 'user_id', 'blog_id' ];

    • 展示评论
      • 首先我们需要确定 Blog 和 Comment 的关系 => Blog 1:n Comment “一篇博客有多个评论”
      • 我们来绑定他们的关系
        • app\Blog.php

        // 绑定1:n关系 public function comments() { return $this->hasMany('App\Comment'); // 1 hasMany n }

        • app\Comment.php

        public function blog() { return $this->belongsTo('App\Blog'); // n belongsTo 1 }

      • 然后通过他们的关系,我们可以在 BlogController@show 方法中调用 $blog->comments 来获取属于这篇文章的评论

      // 查询评论 $comments = $blog->comments; // 视图渲染 return view('blog.show', [ 'blog' => $blog, 'comments' => $comments, //把评论也传过去 ]);

      • 在视图层遍历评论 show.blade.php

      <h3>评论</h3> <ul> @foreach ($comments as $comment) <li><small>{{ $comment->userName() }} 评论说:</small>“ {{ $comment->content }} ”</li> @endforeach</ul>

      • 注意我们调用了 $comment->userName() 方法,现在没有,所以我们再 app\Comment.php 模型中完成

      use App\User; //引用模型 // 根据 user_id 获取用户名 public function userName() { return User::find($this->user_id)->name; //这里通过当前对象的 user_id 获取 user对象, 然后指向->name属性 }

    • 评论验证
    • 新建一个请求Request php artisan make:request CommentRequest

    • 新建的请求位于 app\Http\Requests\ 下,编辑 CommentRequest

      // 1、 开启授权 public function authorize(){ return true; //如果返回false则所有请求都无法生效,会告诉你没有授权(其实在这里面我们是需要去进行判断的,但是这里的逻辑很简单:只有登陆才能查看文章详情,才能看到文章详情下面发表评论的表单,才能发表评论。)所以我们这里直接 return true;}// 2、 编辑规则public function rules(){ return [ 'content' => 'required|min:15|max:100', //这里你可以定义规则我的是:必填、最少15字、最多100字 ]; }

      • 在 CommentController@store 方法的参数列表中通过 CommentRequest 构造 $request, 自动完成校验

      public function store(CommentRequest $request) // 这将 Request 改成 CommentRequest 就会自动调用 CommentRequest@rules 来校验请求的数据了 { Comment::create($request->post()); session()->flash('success', '评论成功!'); return redirect()->back(); }

      • 优化视图 show.blade.php

      {{-- 样式里面加一个判断,判断是否有关于content的错误有的话给样式给文本域加一个红边边 --}} <textarea id="content" class="form-control {{ $errors->has('content') ? ' is-invalid' : '' }}" cols="30" rows="10" name="content">你对文章有什么看法呢?</textarea> {{-- 如果有错误,再显示一个小的错误提示信息 --}} @if ($errors->has('content')) <span class="invalid-feedback"> <strong>{{ $errors->first('content') }}</strong> </span> @endif

      • 此时提交表单,左下角会提醒你 “内容不能为空”,如果你想改“内容两个字”,可以打开 /resources/lang/zh-CN/validation.php

      'content' => '内容', //这里就是配置字段的中文名,你把它改成评论即可。

      • 有时候文章过长,导致提交了,往下拉才看得到文本域变红,所以我们需要新建一个错误组件../components/_error.blade.php

      {{-- 判断是否有错误 --}} @if (count($errors) > 0) <div class="alert alert-danger"> {{-- 遍历所有错误 --}} @foreach ($errors->all() as $error) <ul> {{-- 打印错误 --}} <li> {{ $error }}</li> </ul> @endforeach </div>

    • @endif

      • 然后在 show.blade.php 中引用 @include('components._error')

    第四阶段总结

    • 我们依然创建资源控制器,但是在路由中使用['only'=>'store'] 让资源路由只暴露指向 CommentController@store 的路由
    • 我们学会了通过hasMany() & belongsTo()绑定模型之间的1:n关系。然后通过文章->评论+s;的方法直接获取了属于某篇文章的所有评论。
    • 我们学会了创建请求Request,并且在它的内部配置验证规则,在控制器层中通过依赖注入的形式验证数据。
    • 一旦表单提交的数据不符合 Request@rules Laravel会自动帮我们生成一个叫 $errors 的数组,它存放着所有的错误信息, 我们在视图上通过判断它是否有 content 字段来判断是否是表单提交的评论有问题,然后修改文本域的样式并且在下方用一个小的提示span显示错误提示信息
    • 错误提示信息显示的是“内容 怎么怎么样...”,我们想把“内容”改成评论只需要修改中文语言包下的validation.php中的'content'字段的别名即可。

    第五阶段 最后总结

    • 想让项目上线,也许你需要
      • 更好看的html排版
      • 重新执行一次 php artisan migrate:rollback
      • 权限认证太水了。你需要学习使用 Policy 来进行更安全和全面的权限认证。
    • 视图方面
      • 我们有通过 auth 生成的模板
      • Laravel 自带的 bootstrap4 + jquery
      • 所以我们解决了css和js的问题 => 我们只是写了一个 “确认删除” 的前端代码
    • 数据库方面
      • 我们有 /database/ 下提供的 3套解决方案 Migration / Factory / Seeder 来帮我们解决数据库管理的问题
      • 因为上面的解决方案,我们甚至只写了 建用户、建表、授权3条 数据库操作语句。
    • 路由方面
      • Auth 自动帮我们生成了用户操作相关路由
      • 我们使用资源路由来映射一个 CURD 控制器
    • 控制器和模型方面,通过命令生成的所有类文件,都几乎帮我们写好了,我们只需要完成里面的逻辑。
    • 当然,我们还有 Request 请求认证 Policy 策略控制等等一些列的特性没有学习,我们也只使用了一次composer,其实在开发Laravel时,我们还可以使用非常多的,支持Laravel的,完善的轮子可以利用。
    标签:Laravel

    本文共计4970个文字,预计阅读时间需要20分钟。

    如何用Laravel在30分钟内快速搭建一个博客系统?

    相关专题内容,请直接提供,避免试图解释、举例或使用数字。

  • 有一个命令可以看到当前所有生效的路由 php artisan route:list ,控制台自动弹出的表格中,Method 表示 请求方法,即
    Route::这里的方法()  ,URI 表示 请求地址,即 Route::action('这里就是uri'), name 表示 路由别名,可以通过 Route::action('uri', 'Controller@function')->name('这里可以定义路由别名')

  • 你会发现,有7条关于 blog 的路由,这就是 Route::resource('blog', 'BlogController') 帮我们生成的。(5个功能7条是因为 添加和编辑多了2条载入视图的路由)

  • 完成增删改查吧: 首先完成 BlogController@index : 展示列表

    • 先来个入口链接,打开 home.blade.php

    {{-- 上面说过这里会添加一个按钮 --}} <a href="{{ route('blog.index') }}" class="btn btn-lg btn-block btn-primary">点击这里查看我的博客</a>

    • 编辑 BlogController@index => 这里还是再提醒一下吧,这是说 BlogController 的 index() 方法。控制器文件都在 app\Http\Controllers 中

    <?php namespace App\Http\Controllers; use App\Blog; //这里是使用命令创建控制器时,通过 --model=Blog 自动帮我们生成的 use Illuminate\Http\Request; class BlogController extends Controller{ public function index() { // 查询数据,并且让查询结果是一个可分页对象 $blogs = Blog::orderBy('created_at', 'desc') // 调用 Blog模型 的静态方法 orderBy('根据created_at字段', '倒叙排序') ->paginate(6); // -> 链式操作:paginate(6) 即数据没页6条 // 跳转到视图并传值 return view('blog.index', [ //第一个参数是说,视图模板是 /resources/views/blog/index.blade.php 'blogs' => $blogs, //这里是说,我们给视图传递一个叫 $'blogs'的变量,值是前面我们查询的数据,也叫$blogs。 ]); // view() 的第二参数也可以使用 view(..., compact('blogs')) }

    • 此时刷新页面当然会报错了,因为我们的视图还不存在,新建文件夹 /resources/views/blog/index.blade.php

    @extends('layouts.app') @section('content') <div class="container"> <div class="row justify-content-center"> <div class="card"> <div class="card-header">这是页面小标题</div> <div class="card-body"> 这里是内容 </div> </div> </div> </div> @endsection

    • 完成视图里面的内容

    <div class="container"> <div class="row justify-content-center"> <div class="card"> <div class="card-header">文章列表</div> <div class="card-body"> <table class="table table-hover table-bordered"> <thead class="bg-info"> <tr> <th>文章标题</th> <th>发布时间</th> <th>相关操作</th> </tr> </thead> <tbody> {{-- 这里通过 @foreach 遍历数据 --}} @foreach ($blogs as $blog) <tr> <td>{{ $blog->title }}</td> <td>{{ $blog->created_at }}</td> <td></td> </tr> @endforeach </tbody> <tfoot> {{-- 这里通过 $blogs->links() 来显示分页按钮 --}} {{ $blogs->links() }} </tfoot> </table> </div> </div> </div> </div>

    • 完成文章添加 添加入口链接, ../layouts/app.blade.php

    {{-- route('路由别名') 在视图上就是一个指向 BlogController@create 的链接 --}} <a href="{{ route('blog.create') }}" class="dropdown-item"> 添加文章 </a>

    • 完成文章的添加 BlogController@create

    public function create(){ return view('blog.create'); //载入视图 }

    • 编辑视图 重点:表单中添加@csrf告诉框架,这是我们自己的表单,不用担心csrf跨站请求伪造的攻击

    @extends('layouts.app') @section('content') <div class="container"> <div class="row justify-content-center"> <div class="card"> <div class="card-header">添加文章</div> <div class="card-body"> {{-- from.method="POST" action="通过 route()函数读取路由别名 " --}} <form method="POST" action="{{ route('blog.store') }}"> {{-- 声明 csrf 令牌 --}} @csrf <div class="form-group"> <label for="title">文章标题</label> <input type="text" class="form-control" id="title" placeholder="请输入文章标题" name="title"> </div> <div class="form-group"> <label for="content">文章内容</label> <textarea id="content" cols="30" rows="10" class="form-control" name="content"></textarea> </div> <button class="btn btn-primary" type="submit">发布新文章</button> </form> </div> </div> </div> </div> @endsection

    • 编辑 BlogController@store

    public function store(Request $request) //这里的 $request 是通过依赖注入的方法实例化的 Request 类的对象,包含的有所有请求的信息 { // 我们只需要调用 Blog模型 的静态方法 create() 插入 $request->post() 数据即可 $blog = Blog::create($request->post()); //改方法的返回值是新插入的数据生成的对象 // redirect() 页面重定向 return redirect()->route('blog.show', $blog); // 这里我们将 $blog 作为参数请求 BlogController@show }

    • 回到页面,点击提交,会发现报错了,Laravel是一个极其注重安全的框架,用户能修改哪些字段,必须要在模型文件中声明,因此打开 app\Blog.php 模型文件

    // 可填字段白名单 protected $fillable = [ 'title', 'content' ];

    • 再次提交,页面一片空白,是因为我们的 BlogController@show 方法还没有写,不过你可以注意到地址栏已经发生了改变。

    • 完成 show 方法

    public function show(Blog $blog) //这里已经通过依赖注入的形式帮我们实例化了 $blog { return view('blog.show', [ 'blog' => $blog, //直接将$blog传给视图进行渲染 ]); }

    • 新建 ../blog/show.blade.php

    @extends('layouts.app') @section('content') <div class="container"> <div class="row justify-content-center"> <div class="card"> <div class="card-header">文章详情</div> <div class="card-body"> <h1 class="text-center">{{ $blog->title }}</h1> <p>发布时间<small>{{ $blog->created_at }}</small></p> <hr> <p> {{ $blog->content }} </p> </div> </div> </div> </div> @endsection

    • 刷新页面,文章就显示出来了。

    • 完成我们的编辑入口链接: 在  ../blog/index.blade.php & show.blade.php 中合理的位置添加一个编辑按钮

    <a href="{{ route('blog.edit', $blog->id) }}" class="btn btn-info">编辑文章</a>

    • 完成 BlogController@edit

    public function edit(Blog $blog){ return view('blog.edit', [ 'blog' => $blog, ]); }

    • 完成视图 重点:action声明文章编号,根据路由要求action在表单中使用@method伪造请求动作类型

    @extends('layouts.app') @section('content') <div class="container"> <div class="row justify-content-center"> <div class="card"> <div class="card-header">编辑文章</div> <div class="card-body"> {{-- action需要声明当前编辑的文章编号$blog->id --}} <form method="POST" action="{{ route('blog.update', $blog->id) }}"> {{-- 声明 csrf 令牌 --}} @csrf {{-- 伪造 PATCH 方法 --}} @method("PATCH") <div class="form-group"> <label for="title">文章标题</label> <input type="text" class="form-control" id="title" placeholder="请输入文章标题" name="title" value="{{ $blog->title }}"> </div> <div class="form-group"> <label for="content">文章内容</label> <textarea id="content" cols="30" rows="10" class="form-control" name="content">{{ $blog->content }}</textarea> </div> <p>发表于<small>{{ $blog->created_at }}</small></p> <p>修改于<small>{{ $blog->updated_at }}</small></p> <button class="btn btn-primary" type="submit">确认编辑</button> </form> </div> </div> </div> </div> @endsection

    • 完成 BlogController@update

    public function update(Request $request, Blog $blog){ $blog->update($request->post()); //调用 $blog对象->update(更新数据组成的数组) 更新 return redirect()->route('blog.show', $blog); }

    • 完成删除功能 在 index.blade.php 和 show.blade.php 合理的位置插入删除按钮

    <a href="javascript:deleteConfirm({{ $blog->id }});" class="btn btn-danger btn-sm">删除文章</a> {{-- 因为删除也需要 csrf 令牌认证,所以弄个表单 --}} <form method="POST" action="{{ route('blog.destroy', $blog->id) }}" id="delete-blog-{{ $blog->id }}"> @csrf {{-- 这里伪造DELETE请求 --}} @method("DELETE") </form>

    • “删除文章” 按钮其实是调用了一个 js 函数,我们在 ../layousts/app.blade.php 中完成

    <script> function deleteConfirm(id) { var confirm = window.confirm('确认要删除这篇文章吗?'); if(confirm === true) { $("#delete-blog-" + id).submit(); //提交表单 }else { window.alert('你选择不删除!'); } } </script>

    • 完成 BlogController@delete 方法

    public function destroy(Blog $blog){ $blog->delete(); return redirect()->route('blog.index'); //跳转到首页 }

    • 增删改查完成。
  • 完善和优化

    • 首先无论增删改查操作,成功后我们没有任何提示,我们使用 session 闪存方法消息吧:

      • 新建组件视图文件夹 /resources/views/components/
      • 然后新建一个组件视图 _message.blade.php => 组件视图我们都用_下划线开头

      <div class="container"> {{-- 遍历 success danger 这两个我们等会会在 session->flash() 方法中设置的"key" --}} @foreach (['success', 'danger'] as $msg) {{-- 当key存在的时候,证明我们给 session flash 闪存里面装载了一次提示信息,那么就显示提示信息 --}} @if (session()->has($msg)) <div class="alert alert-{{ $msg }}"> <ul> <li>{{ session()->get($msg) }}</li> </ul> </div> @endif @endforeach </div>

      • 在 ../layousts/app.blade.php 中导入该组件 重点:@include 导入html片段

      {{-- 在导航下面,内容上面导入 --}}@include('components._message')

      • 编辑 BlogController 里的各种方法,在执行成功某些方法时,页面重定向前,装载闪存。以删除举例

      $blog->delete(); session()->flash('success', '删除文章成功!'); //装载session闪存 return redirect()->route('blog.index');

    • 然后有个问题,就是在于,我们这是一个个人博客,所以只有我们自己可以对博客文章进行增删改,而用户只可以进行查看。因此我们需要:

      • 使用构造函数调用 auth中间件 来排除没有登陆的用户查看文章详情: 编辑 BlogController

      public function __construct(){ $this->middleware('auth')->except('index'); }

      • 在 新增create、编辑edit、和删除方法中加入一次用户认证,以 create 方法举例

      // 因为比较简单,所以我们不用Policy进行认证,我会在以后的教程里面教大家如何使用Policy策略进行权限认证 // 这里我们就使用判断当前用户在数据表中信息的主键id是不是1即可(因为我们在Seeder里面把编号为1的用户设置为了可用的管理员账号) // 1、在代码开头引用 Auth // 2、在方法内先判断一下是不是 1号用户 if(Auth::user()->id != 1) { // Auth::user() 获取当前用户信息 -> id获取属性id(主键) session()->flash('danger', '抱歉,只有博主才可以新增文章!'); return redirect()->back(); }

  • 针对博客的增删改查在这里就结束了。

  • 第三阶段总结

    • 我们使用命令创建了一个 “资源控制器”
    • 我们在 /routes/web.php 定义了一条资源路由
    • 我们使用 BlogController 中的7个方法完成了对 博客文章 的 CURD(增删改查)操作。
    • 我们优化了一下体验,使用 session()->flash() 装载闪存信息,用一个组件html片段加载信息,最后用@include()在模板上加载这个html组件。
    • 我们最后增加了一个简单的权限认证,判断进行增删改的用户是不是管理员,不是管理员则不允许操作,直接装载一条错误提示闪存,然后返回。

    第四阶段 评论功能

    • 新建一个评论资源控制器 php artisan make:controller CommentController --model=Commment
    • 新增一条资源路由,但只支持发表 /routes/web.php Route::resource('comment', 'CommentController', ['only' => 'store']);
    • 在文章详情页面下方增加一个表单 show.blade.php

    <form method="POST" action="{{ route('comment.store') }}"> @csrf <input type="hidden" name="user_id" value="{{ Auth::user()->id }}"> <input type="hidden" name="blog_id" value="{{ $blog->id }}"> <div class="form-group"> <label for="content"></label> <textarea id="content" class="form-control" cols="30" rows="10" name="content">您对这篇文章有什么看法呢?</textarea> </div> <button class="btn btn-primary" type="submit">发表评论</button> </form>

    • 编辑 CommentController@store

    public function store(Request $request){ Comment::create($request->post()); session()->flash('success', '评论成功!'); return redirect()->back(); }

    • 提交评论出错了,又忘了写可填字段白名单,编辑模型 app\Comment

    protected $fillable = [ 'content', 'user_id', 'blog_id' ];

    • 展示评论
      • 首先我们需要确定 Blog 和 Comment 的关系 => Blog 1:n Comment “一篇博客有多个评论”
      • 我们来绑定他们的关系
        • app\Blog.php

        // 绑定1:n关系 public function comments() { return $this->hasMany('App\Comment'); // 1 hasMany n }

        • app\Comment.php

        public function blog() { return $this->belongsTo('App\Blog'); // n belongsTo 1 }

      • 然后通过他们的关系,我们可以在 BlogController@show 方法中调用 $blog->comments 来获取属于这篇文章的评论

      // 查询评论 $comments = $blog->comments; // 视图渲染 return view('blog.show', [ 'blog' => $blog, 'comments' => $comments, //把评论也传过去 ]);

      • 在视图层遍历评论 show.blade.php

      <h3>评论</h3> <ul> @foreach ($comments as $comment) <li><small>{{ $comment->userName() }} 评论说:</small>“ {{ $comment->content }} ”</li> @endforeach</ul>

      • 注意我们调用了 $comment->userName() 方法,现在没有,所以我们再 app\Comment.php 模型中完成

      use App\User; //引用模型 // 根据 user_id 获取用户名 public function userName() { return User::find($this->user_id)->name; //这里通过当前对象的 user_id 获取 user对象, 然后指向->name属性 }

    • 评论验证
    • 新建一个请求Request php artisan make:request CommentRequest

    • 新建的请求位于 app\Http\Requests\ 下,编辑 CommentRequest

      // 1、 开启授权 public function authorize(){ return true; //如果返回false则所有请求都无法生效,会告诉你没有授权(其实在这里面我们是需要去进行判断的,但是这里的逻辑很简单:只有登陆才能查看文章详情,才能看到文章详情下面发表评论的表单,才能发表评论。)所以我们这里直接 return true;}// 2、 编辑规则public function rules(){ return [ 'content' => 'required|min:15|max:100', //这里你可以定义规则我的是:必填、最少15字、最多100字 ]; }

      • 在 CommentController@store 方法的参数列表中通过 CommentRequest 构造 $request, 自动完成校验

      public function store(CommentRequest $request) // 这将 Request 改成 CommentRequest 就会自动调用 CommentRequest@rules 来校验请求的数据了 { Comment::create($request->post()); session()->flash('success', '评论成功!'); return redirect()->back(); }

      • 优化视图 show.blade.php

      {{-- 样式里面加一个判断,判断是否有关于content的错误有的话给样式给文本域加一个红边边 --}} <textarea id="content" class="form-control {{ $errors->has('content') ? ' is-invalid' : '' }}" cols="30" rows="10" name="content">你对文章有什么看法呢?</textarea> {{-- 如果有错误,再显示一个小的错误提示信息 --}} @if ($errors->has('content')) <span class="invalid-feedback"> <strong>{{ $errors->first('content') }}</strong> </span> @endif

      • 此时提交表单,左下角会提醒你 “内容不能为空”,如果你想改“内容两个字”,可以打开 /resources/lang/zh-CN/validation.php

      'content' => '内容', //这里就是配置字段的中文名,你把它改成评论即可。

      • 有时候文章过长,导致提交了,往下拉才看得到文本域变红,所以我们需要新建一个错误组件../components/_error.blade.php

      {{-- 判断是否有错误 --}} @if (count($errors) > 0) <div class="alert alert-danger"> {{-- 遍历所有错误 --}} @foreach ($errors->all() as $error) <ul> {{-- 打印错误 --}} <li> {{ $error }}</li> </ul> @endforeach </div>

    • @endif

      • 然后在 show.blade.php 中引用 @include('components._error')

    第四阶段总结

    • 我们依然创建资源控制器,但是在路由中使用['only'=>'store'] 让资源路由只暴露指向 CommentController@store 的路由
    • 我们学会了通过hasMany() & belongsTo()绑定模型之间的1:n关系。然后通过文章->评论+s;的方法直接获取了属于某篇文章的所有评论。
    • 我们学会了创建请求Request,并且在它的内部配置验证规则,在控制器层中通过依赖注入的形式验证数据。
    • 一旦表单提交的数据不符合 Request@rules Laravel会自动帮我们生成一个叫 $errors 的数组,它存放着所有的错误信息, 我们在视图上通过判断它是否有 content 字段来判断是否是表单提交的评论有问题,然后修改文本域的样式并且在下方用一个小的提示span显示错误提示信息
    • 错误提示信息显示的是“内容 怎么怎么样...”,我们想把“内容”改成评论只需要修改中文语言包下的validation.php中的'content'字段的别名即可。

    第五阶段 最后总结

    • 想让项目上线,也许你需要
      • 更好看的html排版
      • 重新执行一次 php artisan migrate:rollback
      • 权限认证太水了。你需要学习使用 Policy 来进行更安全和全面的权限认证。
    • 视图方面
      • 我们有通过 auth 生成的模板
      • Laravel 自带的 bootstrap4 + jquery
      • 所以我们解决了css和js的问题 => 我们只是写了一个 “确认删除” 的前端代码
    • 数据库方面
      • 我们有 /database/ 下提供的 3套解决方案 Migration / Factory / Seeder 来帮我们解决数据库管理的问题
      • 因为上面的解决方案,我们甚至只写了 建用户、建表、授权3条 数据库操作语句。
    • 路由方面
      • Auth 自动帮我们生成了用户操作相关路由
      • 我们使用资源路由来映射一个 CURD 控制器
    • 控制器和模型方面,通过命令生成的所有类文件,都几乎帮我们写好了,我们只需要完成里面的逻辑。
    • 当然,我们还有 Request 请求认证 Policy 策略控制等等一些列的特性没有学习,我们也只使用了一次composer,其实在开发Laravel时,我们还可以使用非常多的,支持Laravel的,完善的轮子可以利用。
    标签:Laravel