介绍
如果你愿意用root用户,你甚至只需要 create 一个 database 即可。(不过不推荐,我的习惯是一个项目 对应 一个用户 + 一个数据库,root则只用来管理他们)
# 创建用户 blog, 密码自定义
CREATE USER 'blog'@'%' IDENTIFIED BY '密码';
# 创建数据库 blog, 设置默认编码为utf8
CREATE DATABASE `blog` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
# 授权 授予 blog库下所有表的 所有权限 给 用户blog
GRANT ALL on blog.* to 'blog'@'%';
使用 composer 创建一个 laravel 项目 取名叫blog
# 进入你本地服务器用于存放网站文档的目录,输入命令
composer create-project --prefer-dist laravel/laravel blog
下文中,“/” 即表示 laravel 框架的根目录
# 数据库配置
DB_CONNECTION=mysql #类型
DB_HOST=127.0.0.1 #ip
DB_PORT=3306 #端口
DB_DATABASE=数据库名
DB_USERNAME=用户名
DB_PASSWORD=密码
composer require caouecs/laravel-lang
然后将 /vendor/caouecs/src/zh-CN/ 放入 /resources/lang/ 下# 时区
'timezone' => 'Asia/Shanghai',
# 语言
'locale' => 'zh-CN',
你可能没有见过上面3个名词,不过和他们有关的文件都存放在 /database/ 下:通过这个文件夹的名称,你大概已经猜到:这三个文件都是用来操作数据库的。
php artisan make:migration create_blogs --create=blogs
博客表,
php artisan make:migration create_comments --create=comments
评论表// 首先类定义中,有两个方法,up()可以理解为正向操作:创建表,而 down()可以理解为回滚操作:删除表。
// up()
Schema::create('blogs', function (Blueprint $table) {
$table->increments('id');
$table->string('title');
$table->text('content');
$table->timestamps();
});
// down() 已经自动帮我们写好了。
create_comments
// up()
Schema::create('comments', function (Blueprint $table) {
$table->increments('id');
$table->string('content');
$table->integer('blog_id'); //这条评论是针对哪一篇博客的?
$table->integer('user_id'); //这条评论是哪一位用户发送的?
$table->timestamps();
});
php artisan migrate
php artisan migrate:rollback
即回滚数据库,将会执行批次batch最大的记录的那些迁移文件的 down() 方法。在日常的开发中,我们需要很多模拟的数据进行测试,模型工厂的作用就是帮我们快速的,随机的生成这些数据。
php artisan make:factory BlogFactory --model=Blog
, 关于评论表的模型工厂请自己写。php artisan make:model Blog
, 关于评论表的模型请自己写。细心的你可能发现了,我们的数据表和模型的名字是有区别的:数据表为“小写复数形式”,而模型名为“大写单数形式”。创建的模型都存在于 /app/ 下。
// 使用 Faker 类为我们提供的生成随机伪造数据的方法生成数据
return [
'title' => $faker->name,
'content' => $faker->text,
];
CommentFactory
return [
'content' => $faker->text,
'blog_id' => 1,
'user_id' => 1,
];
php artisan tinker
, 当命令提示符变为 ">>>" 时,你就处于tinker模式下了,此时你可以输入php代码,或者调用laravel提供的全局函数,甚至引用一个类,调用它的静态方法或者实例化它。factory(App\Blog::class)->make()
此时屏幕上会显示,它给你模拟出来的一个虚拟数据数组。factory(App\Blog::class, 100)->create()
打开数据库,您会发现100条标题和内容都无关紧要,但是对我们快速开发特别有用的测试数据已经存放在数据库中了。php artisan make:seeder UserTableSeeder
,针对博客表和评论表的Seeder创建命令自己写。...
use App\User;
// 在 class 关键字前面,引用一下 User 模型
class ...
public function run()
{
factory(App\User::class, 50)->create(); //向users表中插入50条模拟数据
$user = User::find(1); //插入完后,找到 id 为 1 的用户
$user->name = "najiuyuanzou"; //设置 用户名
$user->email = "najiuyuanzou@test.com"; //设置 邮箱
$user->password = bcrypt('liuhaoyu'); //设置 密码
$user->save(); //保存
}
在这里我们明确1号用户为真实的可用的管理员用户!所以我们设置一下它的 用户名 邮箱 以及密码
public function run(){
// $this->call(UsersTableSeeder::class);
// 这里给我们举例了如何引用其他Seeder
$this->call(UserTableSeeder::class);
$this->call(BlogTableSeeder::class);
$this->call(CommentTableSeeder::class);// 这里有先后关系需要注意一下:评论n : 1文章/用户,所以应该把它写在最后
}
php artisan migrate:refresh --seed
=> 查看数据库,发现数据库重置了,并且 users blogs comments 每张表都有很多虚拟的数据。也许你到这里会觉得这还不如你写sql语句。但是请相信我,等你熟练掌握使用这些东西之后,你的开发速度会非常快!(毕竟你不需要再 "INSERT INTO table values ()" 复制粘贴修改100遍了)
php artisan make:auth
php artisan make:auth
到底干了哪些事情呢?
Auth::routes(); //这是用户操作相关的路由 Route::get('/home', 'HomeController@index')->name('home'); //这是主页的路由// Route::action('uri', 'Controller@function');
Route::get('/', 'HomeController@index');
Auth::routes();
Route::get('/home', 'HomeController@index')->name('home');
控制器方面 app/Http/Controllers/ 我们修改一下 HomeController.php
/**
* 这里这个构造函数调用了 中间件auth 对我们进行权限认证
* 即要求我们必须登陆才可以访问该控制器的其他方法
* 有两种解决方法,一直是在 $this->middleware('auth')->except('你要排除权限认证的方法'),比如 ...->except('index')
* 另一种是直接干掉这个函数(我们确定这个控制器就只是来展示首页的,那么就干掉它吧)
*/
// public function __construct()
// {
// $this->middleware('auth');
// }
视图方面:HomeController@index (这里我指的是 Home控制器的 index() 方法)调用了 return view('视图名称')
来抓取视图显示在页面上,现在打开浏览器访问主页,你就可以看得到 home.blade.php 中的内容了,我们看看 /resources/views/home.blade.php 的内容: 重点:@extens @section
{{-- 内容不重要我们等下要改,先来看下 @符号 的作用 --}}
{{-- @extends继承其他模板 --}}
@extends('layouts.app') {{-- 这里的layouts.app => /resources/views/layouts/app.blade.php --}}
{{-- @section 填充在布局模板上用 @yield 标注的占位符 --}}
@section('content') {{-- 你可以在 /resources/views/layouts/app.blade.php 看到 @ yield('content')标注的占位符 --}}
... 这里面是html内容
@endsection
Auth为我们生成的整个视图模板的逻辑:layouts/app.blade.php 为布局模板,其他模板都继承该模板。
@extends('layouts.app')
@section('content')<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">欢迎!这里是 “那就远走” 的个人博客</div>
<div class="card-body">
如无牵挂,那就远走。
{{-- 这里等下要添加一个跳转到展示文章列表页面的按钮 --}}
</div>
</div>
</div>
</div>
</div>
@endsection
../layouts/app.blade.php & ../auth/login.blade.php & register.blade.php
# 首先布局模板我们需要把 brand 登陆、注册这些东西改一改
{{ config('app.name', 'Laravel') }} => 我的博客 //注意这里有一个 config('app.name') 该函数其实是读取的 /.env 里的 APP_NAME 值,且默认值为 'Laravel' ,也就是说,你改 APP_NAME 也可以改这里显示的值,不过我嫌麻烦,直接查找替换了。
{{ __('Login') }} => 登陆
{{ __('Register') }} => 注册
{{ __('Logout') }} => 退出
# 然后登陆模板:
{{ __('Login') }} => 登陆
{{ __('E-Mail Address') }} => 邮箱
{{ __('Password') }} => 密码
{{ __('Remember Me') }} => 记住我
然后我们把 ForgetPassword 那个按钮给干掉吧(这个找回密码的功能需要一个SMTP服务的邮箱才能实现,现在暂时不弄)
# 然后注册模板
{{ __('Register') }} => 注册
{{ __('Name') }} => 昵称(用户名)
{{ __('Confirm Password') }} => 密码确认
# 有可能有说漏的,反正自己看着页面上的英文查找替换成中文就可以了。
Route::get('home', 'HomeController@home')
=> 即表示,你输入 "http://localhost/blog/public/home" 是以GET的请求方式去请求 HomeController 的 home() 方法。return view()
调用和渲染它,最终展示给网站访客。我们写的程序,除了前台好看的界面,就是后台的程序,而后台的程序无非就是“增删改查”以及“花式增删改查”罢了。 因此,仔细想想,对于一张数据表的操作,我们通常就需要这些行为:1、一个分页展示所有数据的列表 2、一个添加数据的功能 3、一个编辑数据的功能 4、一个显示单条数据详细信息的功能 5、一个删除功能。
php artisan make:controller BlogController --resource --model=Blog
( --resouce生成的控制器为资源控制器即自带 CURD增删改查 所有方法的控制器 ) ( --model 是让生成的控制器在参数列表中自动帮我们完成依赖注入生成实际变量 )Route::resource('blog', 'BlogController');
php artisan route:list
,控制台自动弹出的表格中,Method 表示 请求方法,即
Route::这里的方法()
,URI 表示 请求地址,即 Route::action('这里就是uri')
, name 表示 路由别名,可以通过 Route::action('uri', 'Controller@function')->name('这里可以定义路由别名')
。Route::resource('blog', 'BlogController')
帮我们生成的。(5个功能7条是因为 添加和编辑多了2条载入视图的路由){{-- 上面说过这里会添加一个按钮 --}}
<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
所谓跨站请求伪造,可以理解为来自于其他ip的表单,恶意请求我们的服务器。Laravel提供了一种防范这种攻击的手段,即将自己的路由隐藏起来,只有带有 @csrf 声明的表单可以找得到接收表单信息的路由
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'
];
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
<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'); //跳转到首页
}
_
下划线开头<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>
{{-- 在导航下面,内容上面导入 --}}@include('components._message')
$blog->delete();
session()->flash('success', '删除文章成功!'); //装载session闪存
return redirect()->route('blog.index');
然后有个问题,就是在于,我们这是一个个人博客,所以只有我们自己可以对博客文章进行增删改,而用户只可以进行查看。因此我们需要:
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();
}
session()->flash()
装载闪存信息,用一个组件html片段加载信息,最后用@include()
在模板上加载这个html组件。php artisan make:controller CommentController --model=Commment
Route::resource('comment', 'CommentController', ['only' => 'store']);
其实我们可以定义一条
Route::post('comment', 'CommentController@store')
路由,但是为什么要写资源路由呢?因为我要告诉你资源路由可以用['onlu'=>'操作']
让其只支持一种操作:)
<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'
];
展示评论
// 绑定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属性
}
在博客中,我们就没有使用验证,那是因为项目定位是一个个人博客,能够操纵博客增删改的只有我们自己。而评论则是只要有人注册账号,就可以评论了,所以我们应该对评论进行一些校验以防恶意攻击。
php artisan make:request 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
['only'=>'store']
让资源路由只暴露指向 CommentController@store 的路由hasMany() & belongsTo()
绑定模型之间的1:n关系。然后通过文章->评论+s;
的方法直接获取了属于某篇文章的所有评论。content
字段来判断是否是表单提交的评论有问题,然后修改文本域的样式并且在下方用一个小的提示span显示错误提示信息php artisan migrate:rollback
也许文字很多,但是真正的代码可能只有不到100行,你如果熟练掌握,可能不需要30分钟,甚至10分钟,你就可以开发出这样一个博客了。
现在请告诉我,它是否配得上 “优雅” 的两字?:)
希望大家可以喜欢、学习和推广Laravel。如果您愿意付出比学习thinkphp5多0.01分的努力,我想这个框架是非常简单的。
如果您依然讨厌它的庞大,我向您推荐 Lumen 框架。