Laravel控制器
php artisan make:controller TaskController
# 通过Artisan命令快速创建一个控制器
# 该命令会在 app/Http/Controllers 目录下创建一个新的名为 TaskController.php 的文件
php artisan make:controller PostController --resource
# 使用 Artisan 生成器来生成一个资源控制器(在之前命名后加上 --resource 选项)
# 打开 app/Http/Controllers/PostController.php 文件,可看到 PostController 代码
Route::resource('post', 'PostController');
# 写在web.php文件中,一次注册包含上面列出的所有路由
php artisan route:list
# Artisan命令,查看应用的所有路由
资源控制器方法列表
路由进阶使用
Route::fallback(function(){
return '我是最后的屏障';
});
# 兜底路由,请求无法匹配到的URL时返回的内容
Route::middleware('throttle:60,1')->group(function(){
Route::get('/user', function(){
//
});
})
# 一分钟能只能访问路由分组的内路由(如 /user)60 次,超过此限制会返回 429 状态码并提示请求过于频繁
# 通过内置的throttle中间件来实现,接受两个参数,第一个是次数上限,另一个是指定时间段(单位:分钟
Route::middleware('throttle:rate_limit,1')->group(function () {
Route::get('/user', function () {
// 在 User 模型中设置自定义的 rate_limit 属性值
});
Route::get('/post', function () {
// 在 Post 模型中设置自定义的 rate_limit 属性值
});
});
php artisan route:cache
# 将所有路由定义转化为控制器路由或资源路由后才能执行路由缓存命令
php artisan route:clear
# 删除路由缓存
表单方法伪造与跨站请求伪造攻击防护
HTML 表单仅支持 GET 和 POST 两种方式,
如果要使用其他的方式,则需要自己来定义实现
/**
* Laravel 路由支持的 HTTP 请求方式
*
* @var array
*/
public static $verbs = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'];
Route::get($uri, $callback);
Route::post($uri, $callback);
Route::put($uri, $callback);
Route::patch($uri, $callback);
Route::delete($uri, $callback);
Route::options($uri, $callback);
# 相应的路由定义方法
<form action="/task/1" method="POST">
<input type="hidden" name="_method" value="DELETE">
</form>
# 表单请求方法伪造
# 在表单中加一个名为'_method'的隐藏字段,字段值是 PUT、DELETE、 PATCH
视图入门
return view('以.分隔的视图模板路径');
# 视图的定义方式
# 使用 view 辅助函数在路由中返回视图
Route::get('/', function () {
// 该函数会在 resources/views 目录下查找 home.blade.php 或 home.php 视图文件,
// 加载文件内容并解析 PHP 变量或语句,然后传递给响应,最终呈现给用户
return view('home');
});
return view('home')->with('tasks', Task::all());
return view('home', ['tasks' => Task::all()]);
# 传递数据给视图的方法,将tasks数据变量传递到视图以便在视图中引用
view()->share('siteName', 'Laravel学院');
view()->share('siteUrl', 'https://xueyuanjun.com');
# 通过视图对象提供的share方法实现在视图间共享变量
# 比如在AppServiceProvider 的 boot 方法中定义共享的视图变量
# 然后就可以在各个视图中使用 $siteName 和 $siteUrl 这两个变量了(其它变量定义方式类似)
Blade入门篇
# Blade模板代码实例
<h1>{{ $group->title }}</h1>
{!! $group->imageHtml() !!}
@forelse ($users as $user)
{{ $user->username }} {{ $user->nickname }}<br>
@empty
该组中没有任何用户
@endforelse
# 通过 {{ }} 渲染 PHP 变量(最常用)
# 通过 {!! !!} 渲染原生 HTML 代码(用于富文本数据渲染)
# 通过以 @ 作为前缀的 Blade 指令执行一些控制结构和继承、引入之类的操作
# Blade 模板代码存放在以 .blade.php 后缀结尾的视图文件中
# 编译后的代码位于 storage/framework/views 目录下
{{ $variable }}
# 编译后的最终代码(避免XSS攻击):
<?php echo htmlentities($variable); ?>
# 对于当前端框架也用{{}}输出js变量的情况
# 需要在渲染前端js变量的{{}}前面加上@前缀
# Blade 引擎会将其编译为对应的 PHP 代码
{{ $phpData }}
# Blade 引擎编译时会移除 @,保留 {{ $vueData }} 结构
@{{ $vueData }}
# 条件语句
@if (count($students) === 1)
操场上只有一个同学
@elseif (count($students) === 0)
操场上一个同学也没有
@else
操场上有 {{ count($students) }} 个同学
@endif
# 表示和 @if 条件相反的条件
@unless ($user->hasPaid())
用户没有支付
@endunless
# 和 isset() empty() 方法等价
@isset($records)
// 记录被设置
@endisset
@empty($records)
// 记录为空
@endempty
@switch($i)
@case(1)
// $i = 1 做什么
@break
@case(2)
// $i = 2 做什么
@break
@default
// 默认情况下做什么
@endswitch
// for 循环
@for ($i = 0; $i < $talk->slotsCount(); $i++)
The number is {{ $i }}<br>
@endfor
// foreach 循环
@foreach ($talks as $talk)
{{ $talk->title }} ({{ $talk->length }} 分钟)<br>
@endforeach
// while 循环
@while ($item = array_pop($items))
{{ $item->orSomething() }}<br>
@endwhile
@forelse ($students as $student)
// do something ...
@empty
// do something else ...
@endforelse
# 用于处理一下的PHP代码逻辑:
<?php
if ($students) {
foreach ($students as $student) {
// do something ...
}
} else {
// do something else ...
}
?>
Blade进阶篇
1.通过
@yield
和@section/@show
在布局文件中定义插槽。
2.通过@extends
和@section/@endsection
在子视图实现继承。
3.通过@include
引入其他视图组件
4.通过@each
指令循环引入单个组件,支持多个参数。
5.通过@slot
和@component
实现更加灵活的内容分发。
Blade高级篇
不知道写啥...
使用Bootstrap、Vue
composer require laravel/ui #通过Composer下载安装
# 初始化前端脚手架
php artisan ui bootstrap
php artisan ui vue
npm install # 安装应用的前端依赖
npm run dev
# 编写Vue组件
# 将Vue组件写在 resources/js/components目录下
# 在resources/js/app.js中全局注册
Vue.component('welcome-component', require('./components/WelcomeComponent.vue').default);
const app = new Vue({
el: '#app'
});
# 完了修改welcome.blade.php代码,再 npm run dev就好啦~
# Vue组件名开头一定要大写
使用Laravel Mix编译前端资源
npm install # 安装需要的依赖
npm run dev # 运行所有 Mix 任务
npm run watch # 监控,发现修改后自动重新编译文件
mix.less('resources/assets/less/app.less', 'public/css'); # 编译 less 文件
mix.less('resources/assets/less/app.less', 'public/stylesheets/styles.css'); # 自定义编译后文件的输出位置
mix.sass('resources/assets/sass/app.scss', 'public/css'); # 编译 sass 文件,同上
mix.stylus('resources/assets/stylus/app.styl', 'public/css'); # 编译 stylus 文件
mix.js('resources/assets/js/app.js', 'public/js'); # 处理js
mix.js('resources/assets/js/app.js', 'public/js')
.extract(['vue']) # 提取 Vendor 库
mix.react('resources/assets/js/app.jsx', 'public/js'); # 支持 React
mix.copy('node_modules/foo/bar.css', 'public/css/bar.css'); # 拷贝文件/目录
mix.js('resources/assets/js/app.js', 'public/js')
.version(); # 缓存刷新
<link rel="stylesheet" href="{{ mix('css/app.css') }}"> # 在视图中使用 Laravel 全局的 Mix 函数加载前端资源
mix.browserSync('my-domain.test');
// Or...
// https://browsersync.io/docs/options
mix.browserSync({
proxy: 'my-domain.test'
});
# 自动监控文件修改
通过迁移文件定义数据表结构
$table->string('name', 100)->comment('用户名'); # 为字段额外添加注释信息
Schema::dropIfExists('user'); # 删除表(在down()方法中
php artisan make:migration alter_users_add_nickname --table=users
# 修改表应在新增迁移文件中操作
$table->string('nickname', 100)->after('name')->nullable()->comment('用户昵称'); # 新增 nickname 字段
$table->dropColumn('nickname'); # 删除一个已存在的字段
composer require doctrine/dbal # 对已存在的数据表字段修改,先安装扩展包
$table->string('nickname', 50)->change(); # 修改字段长度
$table->renameColumn('name', 'username'); # 重命名字段
$table->string('email')->unique(); # 添加唯一索引
$table->increments('id'); # 创建自动增长的主键索引
$table->index(['name', 'email']); # 支持一次传入多个字段,构建混合索引
# ↓不推荐使用,影响数据库性能
$table->foreign('user_id')->references('id')->on('users');
# 建立文章表中的 user_id 字段与用户表中的 id 之间的关联关系
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->foreign('user_id')->references('id')->on('users')->onUpdate('cascade');
# 外键约束
$table->dropForeign(['user_id']); # 删除外键索引
php artisan migrate # 执行变更
php artisan migrate:rollback # 回滚最后一个迁移文件的变更
php artisan migrate:rollback --step=5 # 回滚多个迁移文件的变更,可以通过 --step= 指定步数
php artisan migrate:reset # 回滚所有迁移文件的变更,将数据库恢复到初始状态
通过填充器快速填充测试数据
database/seeds/DatabaseSeeder.php
php artisan db:seed # 一次性调用所有填充器
# 需要将所有对其他填充器的调用定义在该方法中↓
$this->call(UsersTableSeeder::class);
php artisan db:seed --class=UsersTableSeeder # 指定某个填充器类的run方法
php artisan make:seeder UsersTableSeeder # 为users表快速创建一个填充器类 UsersTableSeeder
eg: # 填充逻辑
public function run()
{
DB::table('users')->insert([
'name' => \Str::random(10),
'email' => \Str::random(10).'@gmail.com',
'password' => bcrypt('secret'),
]);
}
# laravel 7 str_random()无效,应改成如上\Str::random
php artisan db:seed --class=UsersTableSeeder # 运行
# 也可以在DatabaseSeeder 类的 run 方法中运行这个填充器类
# 适用于多个填充器类一次运行的情况↓
public function run()
{
$this->call(UsersTableSeeder::class);
}
# 然后↓
php artisan db:seed
# Laravel 自带一个用于填充 User 模型的模型工厂 UserFactory.php
php artisan make:factory PostFactory --model=Post # 为模型类创建一个模型工厂
# 在UsersTableSeeder 的 run 方法中通过模型工厂改写数据填充方法↓
# 传入对应模型类和要填充的记录数
factory(\App\User::class, 5)->create();
php artisan db:seed --class=UsersTableSeeder
# 运行命令来填充数据库
通过查询构建器实现简单的增删改查操作
在laravel中不要通过 DB
门面提供的 statement
方法执行原生的 SQL Statement 语句,比如创建、删除、修改数据表操作,这些操作可以通过数据库迁移来实现,而且可维护性更好。
# 写在控制器里
$users = DB::select('select * from `users`'); # 查,返回所有结果的对象数组
# 指定查询条件,借助 PDO 的参数绑定功能来防范 SQL 注入
$name = 'kong';
$users = DB::select('select * from `users` where `name` = ?', [$name]);
# 改
$name = '空空';
$id = 1;
$affectedRows = DB::update('update `users` set `name` = ? where id = ?', [$name, $id]);
# 删
$id = 7;
$affectedRows = DB::delete('delete from `users` where id = ?', [$id]);
# 使用查询构建器进行增删改查
$users = DB::table('users')->get(); # 查
# 指定查询条件,无需手动设置参数绑定来规避 SQL 注入攻击
$name = '空空';
$users = DB::table('users')->where('name', $name)->get();
$user = DB::table('users')->where('name', $name)->first(); # 返回查询结果中的第一条记录
$user = DB::table('users')->select('id', 'name', 'email')->where('name', $name)->first(); # 指定查询的字段
# 增/插入记录
$flag = DB::table('users')->insert([
'name' => \Str_random(10),
'email' => \Str_random(8) . '@163.com',
'password' => bcrypt('secret')
]);
# 插入之后获取对应记录的主键 ID
$userId = DB::table('users')->insertGetId([
'name' => \Str_random(10),
'email' => \Str_random(8) . '@qq.com',
'password' => bcrypt('secret')
]);
# 支持一次插入多条记录
# 改/更新
$id = 11;
$affectedRows = DB::table('users')->where('id', '>', $id)->update(['name' => \Str_random(8)]);
# where 方法第二个参数省略的话,默认是 =
# 如果不是相等条件需要手动指定该参数值, > 大于,< 小于,和比较运算符一致
# 删
$id = 11;
$affectedRows = DB::table('users')->where('id', '>=', $id)->delete();
$affectedRows = DB::table('users')->delete(); # 清空
$affectedRows = DB::table('users')->truncate(); # 清空后重置自增ID
通过查询构建器实现复杂的查询语句
$name = '空空';
$email = DB::table('users')->where('name', $name)->value('email'); # 直接获取指定字段的值
$exists = DB::table('users')->where('name', $name)->exists(); # 判断字段值是否存在
$users = DB::table('users')->where('id', '<', 10)->pluck('name', 'id'); # 构建关联数组
# ↑键对应的字段在后面,值对应的字段在前面
# 避免一次返回所有值超出php内存限制的办法
# 对 users 按照 id 字段升序排序后将获取的结果集每次返回5个进行处理,将用户名依次放到 $names 数组中
$names = [];
DB::table('users')->orderBy('id')->chunk(5, function ($users) use (&$names) {
foreach ($users as $user) {
$names[] = $user->name;
}
});
# 聚合函数
$num = DB::table('users')->count(); # 计数 9
$sum = DB::table('users')->sum('id'); # 求和 45
$avg = DB::table('users')->avg('id'); # 平均值 5
$min = DB::table('users')->min('id'); # 最小值 1
$max = DB::table('users')->max('id'); # 最大值 9
# 基础查询
DB::table('posts')->where('views', 0)->get(); # 此处等号可以省略
DB::table('posts')->where('views', '>', 0)->get(); # 字段名,运算符,比较值
DB::table('posts')->where('title', 'like', 'Laravel学院%')->get(); # 模糊查询
DB::table('posts')->where('id', '<', 10)->where('views', '>', 0)->get(); # 多个条件
DB::table('posts')->where([
['id', '<', 10],
['views', '>', 0]
])->get(); # 另一种写法
DB::table('posts')->where('id', '<', 10)->orWhere('views', '>', 0)->get(); # or条件的查询
DB::table('users')->whereBetween('id', [2, 4])->get(); # between查询
DB::table('users')->whereNotBetween('id', [2, 4])->get(); # 获取不在指定区间的记录
DB::table('posts')->whereIn('id', [1, 3, 5, 7, 9])->get(); # in查询,还有whereNotIn
DB::table('users')->whereNull('email')->get(); # NULL查询,还有whereNotNull
# 日期查询
DB::table('posts')->whereYear('created_at', '2018')->get(); # 年
DB::table('posts')->whereMonth('created_at', '11')->get(); # 月
DB::table('posts')->whereDay('created_at', '28')->get(); # 一个月的第几天
DB::table('posts')->whereDate('created_at', '2018-11-28')->get(); # 具体日期
DB::table('posts')->whereTime('created_at', '14:00')->get(); # 时间
DB::table('posts')->whereColumn('updated_at', '>', 'created_at')->get(); # 字段相等查询
DB::table('users')
->where('options->language', 'en')
->get(); # JSON属性查询
DB::table('users')
->whereJsonContains('options->languages', 'en_US')
->get();
DB::table('users')
->whereJsonContains('options->languages', ['en_US', 'zh_CN'])
->get(); # 数组的包含查询
# 参数分组
select * from posts where id <= 10 or (views > 0 and created_at < '2018-11-28 14:00');
# 查询构建器的实现↓
DB::table('posts')->where('id', '<=', 10)->orWhere(function ($query) {
$query->where('views', '>', 0)
->whereDate('created_at', '<', '2018-11-28')
->whereTime('created_at', '<', '14:00');
})->get();
# where exists
select * from `users` where exists (select 1 from `posts` where posts.user_id = users.id);
# 查询构建器的实现↓
DB::table('users')
->whereExists(function ($query) {
$query->select(DB::raw(1))
->from('posts')
->whereRaw('posts.user_id = users.id');
})
->get();
# 子查询
select * from posts where user_id in (select id from users where email_verified_at is not null);
# 查询构建器的实现↓
$users = DB::table('users')->whereNotNull('email_verified_at')->select('id');
$posts = DB::table('posts')->whereInSub('user_id', $users)->get();
创建并填充posts表
php artisan make:migration create_posts_table --create=posts # 新建 posts 数据表
# 在CreatePostsTable 的 up里:
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->increments('id');
$table->string('title')->comment('标题');
$table->text('content')->comment('内容');
$table->integer('user_id')->unsigned()->default(0);
$table->integer('views')->unsigned()->default(0)->comment('浏览数');
$table->index('user_id');
$table->timestamps();
});
}
php artisan migrate # 创建表
php artisan make:model Post # 创建模型类
php artisan make:factory PostFactory --model=Post # 创建模型工厂
# database/factories/PostFactory.php:
$factory->define(\App\Post::class, function (Faker $faker) {
return [
'title' => $faker->title,
'content' => $faker->text,
'user_id' => mt_rand(1, 15),
'views' => $faker->randomDigit
];
});
php artisan make:seeder PostsTableSeeder # 为posts表创建填充类
# PostsTableSeeder里:
public function run()
{
factory(\App\Post::class, 30)->create();
}
php artisan db:seed --class=PostsTableSeeder # 填充 posts 数据表
# 内连接
$posts = DB::table('posts')
->join('users', 'users.id', '=', 'posts.user_id')
->select('posts.*', 'users.name', 'users.email')
->get();
# 当两张表有字段名相同的字段并被select指定时,要区别名
select('posts.*', 'users.name as username', 'users.email')
# 左连接
$posts = DB::table('posts')
->leftJoin('users', 'users.id', '=', 'posts.user_id')
->select('posts.*', 'users.name', 'users.email')
->get();
# 右连接
$posts = DB::table('posts')
->rightJoin('users', 'users.id', '=', 'posts.user_id')
->select('posts.*', 'users.name', 'users.email')
->get();
DB::table('posts')->orderBy('created_at', 'desc')->get(); # # 按照创建时间进行逆序排序
DB::table('posts')->orderBy('created_at')->get(); # 升序排序
DB::table('posts')->inRandomOrder()->get(); # 随机排序(结果集很大的不要用,性能比较差
# 分组
$posts = DB::table('posts')
->groupBy('user_id')
->selectRaw('user_id, sum(views) as total_views')
->having('total_views', '>=', 10) # 对分组结果进行过滤
->get();
# 分页
$posts = DB::table('posts')->orderBy('created_at', 'desc')
->where('views', '>', 0)
->skip(10) # 表示从第几条记录开始,也可以用 offset()
->take(5) # 表示一次获取多少条记录,也可以用 limit()
->get();
通过 Eloquent 模型实现简单增删改查操作
Eloquent 约定模型类映射表名是将类名由驼峰格式转化为小写+下划线(含多个单词的话),最后将其转化为复数形式,比如 Post
对应表名是 posts
、PostTag
对应表名是 post_tags
。
Eloquent 默认约定每张表都有 created_at
和 updated_at
字段(迁移类中 $table->timestamps()
会生成这两个字段),并且在保存模型类时会自动维护这两个字段。
# 创建模型类
php artisan make:model Post # 这样生成Post.php在app目录下
php artisan make:model Models/Post # 如果要指定创建的model到某个目录,可以先`目录名称/model名字`,这里会将Post.php存放到app/Models目录下
protected $primaryKey = 'post_id'; # 设置自增主键,默认为id
public $incrementing = false; # 设置非自增主键
protected $keyType = 'string'; # 设置非整型主键
public $timestamps = false; # 不设置时间戳
public const CREATED_AT = 'create_time';
public const UPDATED_AT = 'update_time'; # 设置自定义的创建和更新时间字段
protected $dateFormat = 'U'; # 自定义时间戳的格式,这里设置为Unix 时间戳
protected $connection = 'connection_name'; # 为模型类指定使用哪个连接
$posts = Post::all(); # 获取一张表的所有记录
foreach ($posts as $post) {
dump($post->title);
} # 获取指定模型类的字段属性,遍历
foreach (Post::cursor() as $post) {
dump($post->title . ':' . $post->content);
} # 每次只获取一条查询结果,最大限度减少内存消耗
$posts = Post::where('views', '>', 0)
->select('id', 'title', 'content') # 指定查询条件和查询字段
->get();
$posts = Post::where('views', '>', 0)
->orderBy('id', 'desc')
->offset(10)
->limit(5)
->get(); # 排序分页
$user = User::where('name', '空空')->first(); # 获取单条记录
$user = User::find(1); # 简化↑
# 聚合结果
$num = User::whereNotNull('email_verified_at')->count(); # 计数
$sum = User::whereNotNull('email_verified_at')->sum('id'); # 求和
$avg = User::whereNotNull('email_verified_at')->avg('id'); # 平均值
$min = User::whereNotNull('email_verified_at')->min('id'); # 最小值
$max = User::whereNotNull('email_verified_at')->max('id'); # 最大值
# 插入数据
$post = new App\Post;
$post->title = '测试文章标题';
$post->content = '测试文章内容';
$post->user_id = 1;
$post->save();
# 更新
$post = Post::find(31);
$post->title = '测试文章标题更新';
$post->save();
Post::where('views', '>', 0)->update(['views' => 100]); # 批量更新
# 删除
$post = Post::find(31);
$post->delete();
# 一次删除多条记录,通过数组传递多个主键 ID
Post::destroy([1,2,3]);
# 删除指定记录
$user = User::where('name', '空空')->fisrt();
$user->delete();
通过 Eloquent 模型实现批量赋值和软删除
$post = new Post([
'title' => '测试文章标题',
'content' => '测试文章内容'
]); # 将待设置属性以关联数组的方式传递构造函数
$post = new Post($request->all()); # 批量赋值,等同于上面的赋值方式
// 注意:白名单与黑名单不可同时使用
/**
*(白名单) 使用批量赋值的属性,只有在这里设置的字段才会批量赋值
* @var array
*/
protected $fillable = [];
/**
* (黑名单) 不使用批量赋值的字段,如果为星号表示所有字段都不赋值
*
* @var array
*/
protected $guarded = ['*'];
# 更新
$post = Post::findOrFail(1); # 获取相应id的数据,没有相应数据会报错404
$post->fill($request->all());
$post->save();
# 通过数据库迁移添加deleted_at字段,实现软删除
php artisan make:migration alter_posts_add_deleted_at --table=posts
# 在迁移文件中:
public function up()
{
Schema::table('posts', function (Blueprint $table) {
$table->softDeletes();
});
}
public function down()
{
Schema::table('posts', function (Blueprint $table) {
$table->dropColumn('deleted_at');
});
}
php artisan migrate # 执行变更
# 在模型类中:
use SoftDeletes;
# 判断是否被删除
$post = Post::findOrFail(2); # 获取指定id数据
$post->delete(); # 删了它!
if ($post->trashed()) {
dump('该记录已删除');
} # 通过 trashed() 查询
$post = Post::onlyTrashed()->where('views', 0)->get(); # 获取被软删除的数据
$post = Post::onlyTrashed()->where('views', 0)->get(); # 物理删除,整条数据直接没咯
在 Eloquent 模型类上设置访问器和修改器
# 要实现这样的逻辑:
if ($user->nickname) {
$user->display_name = $user->nickname; // 显示用户名为用户昵称
} else {
$user->display_name = $user->name; // 显示用户名为注册时的用户名
}
# 应在user模型类中设置对应方法(访问器
public function getDisplayNameAttribute()
{
return $this->nickname ? $this->nickname : $this->name;
}
# 方法名不能设置为 getNameAttribute之类的,这样会与数据库字段重复,报错
# 在字段值保存到数据库之前进行一定处理满足需求后再存(修改器
public function setCardNoAttribute($value)
{
$value = str_replace(' ', '', $value); // 将所有空格去掉
$this->attributes['card_no'] = encrypt($value);
}
# 通过模型类保存一个银行卡号到数据库
$user = User::find(1);
$user->card_no = '6222020903001483077';
$user->save();
# 数据&JSON转化
# 新建字段settings(MySQL 5.7以下的版本设置字段类型为TEXT格式
protected $casts = [
'settings' => 'array'
];
laravel查看数据库的版本dd(DB::select("select version()"));
Eloquent 模型事件和监听方式大全
所有支持的模型事件:
-
retrieved
:获取到模型实例后触发 -
creating
:插入到数据库前触发 -
created
:插入到数据库后触发 -
updating
:更新到数据库前触发 -
updated
:更新到数据库后触发 -
saving
:保存到数据库前触发(插入/更新之前,无论插入还是更新都会触发) -
saved
:保存到数据库后触发(插入/更新之后,无论插入还是更新都会触发) -
deleting
:从数据库删除记录前触发 -
deleted
:从数据库删除记录后触发 -
restoring
:恢复软删除记录前触发 -
restored
:恢复软删除记录后触
Eloquent 模型关联关系
1.通过数据库迁移新建表+创建对应模型
2.在新表中添加相应字段用于建立关联
3.通过模型类建立两表之间的关联
4.通过填充器写入数据
5.通过关联方法名作为动态属性访问模型实例
一对一,以user
和user_profile
为例。
# 1.一次完成创建表&对应模型
$ php artisan make:model UserProfile -m
# 2.在 user_profile 中新建 user_id 字段
# 3.建立关联,这里是在 User 模型类中定义关联
public function profile()
{
return $this->hasOne(UserProfile::class);
}
# 5.访问实例
$user = User::findOrFail(1);
$profile = $user->profile;
# 建立相对的关联关系,通过UserProfile 反查所属的 User 模型
# 3.在 UserProfile 模型类定义与 User 模型的关联
public function user()
{
return $this->belongsTo(User::class);
}
# 5.访问
$profile = UserProfile::findOrFail(2);
$user = $profile->user;
一对多,以 users
和 posts
表为例。
# 3.在 `User` 模型类中用 `hasMany` 方法建立关联
public function posts()
{
return $this->hasMany(Post::class);
}
# 5.访问
$user = User::findOrFail(1);
$posts = $user->posts;
# 建立相对的关联关系,比如在文章详细页或列表页显示作者信息
# 3.
public function user()
{
return $this->belongsTo(User::class);
}
# 5.
$post = Post::findOrFail(29);
$author = $post->user;
# 还可以将 Post 模型中的关联关系调方法名修改为 author
# 这样可手动指定传入的参数
# 3.
public function author()
{
return $this->belongsTo(User::class, 'user_id', 'id', 'author');
}
# 5.
$author = $post->author;
与 hasOne
返回的是单个模型实例不一样,hasMany
返回的是模型类集合。
多对多,以文章标签为例(posts
、 tags
、 post_tags
# 2. 在中间表(post_tags)中新建 post_id 、tag_id 字段
# 3.在 Post 模型中定义其与 Tags 模型类的关联关系
public function tags()
{
return $this->belongsToMany(Tag::class, 'post_tags');
}
# 5.
$post = Post::findOrFail(1);
$tags = $post->tags;
# 建立相对的关联关系
# 3. 在 Tag 模型类中建立与 Post 模型的关联关系
public function posts()
{
return $this->belongsToMany(Post::class, 'post_tags');
}
# 5.
$tag = Tag::with('posts')->where('name', 'ab')->first();
$posts = $tag->posts;
# 返回中间表字段
$post->pivot->tag_id
# 返回额外字段,在建立关联关系时手动指定
public function tags()
{
return $this->belongsToMany(Tag::class, 'post_tags')->withPivot('user_id')->withTimestamps();
}
远层一对多关联,以 users
、 posts
、 countries
为例
# 1.2.新建 country 模型及表,为 users 表新增 country_id 字段
# 3. 在 Country 模型类中定义关联
public function posts()
{
return $this->hasManyThrough(Post::class, User::class);
} # 第一个参数:关联的模型类,第二个:中间借助的模型类
# 5.
$country = Country::findOrFail(1);
$posts = $country->posts;
# countries(user_id) - user(country_id) - posts(user_id)
一对一的多态关联,以 images
、user
、 posts
为例,让用户和文章共享与图片的一对一关联。
# 1.2.新建 Image 模型类及表
# 创建 imageable_id 和 imageable_type 两个字段
# imageable_type 插入完整的类名以表类型 App\User 或 App\Post
$table->morphs('imageable');
# 3.
public function imageable()
{
return $this->morphTo();
}
#5.
$image = Image::findOrFail(1);
$item = $image->imageable;
# 定义相对的关联关系
# 3.
# 在 User 模型中定义与 Image 模型的关联
public function image()
{
return $this->morphOne(Image::class, 'imageable');
}
# 在 Post 模型中定义其与 Image 模型的关联
public function image()
{
return $this->morphOne(Image::class, 'imageable');
}
# 5.
$post = Post::findOrFail(1);
$image = $post->image;
(插眼)一对多的多态关联,新建 comment 评论模型类。
#3.
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
模型关联关系(下)
懒惰式加载
# 基于关联查询过滤模型实例
# 有结果过滤
$users = User::has('posts')->get(); # 获取所有发发布过文章的用户
$users = User::has('posts', '>', 1)->get(); # 过滤发布文章数量大于 1 的用户
$users = User::has('posts.comments')->get(); # 过滤发布的文章有评论的用户
$posts = Post::has('comments')->orHas('tags')->get(); # 过滤包含评论或标签的文章
$users = User::whereHas('posts', function ($query) {
$query->where('title', 'like', '空空%');
})->get(); # 基于闭包函数定义查询条件
# 无结果过滤
# 与 has/orHas 方法相对的 doesntHave/orDoesntHave 方法
# 不加载关联模型的情况下统计关联结果的数量
# 通过 Eloquent 提供的 withCount 方法
$post = Post::withCount('comments')->findOrFail(32); # 统计某篇文章的评论数
渴求式加载
$post = Post::with('author')->findOrFail(1);
$author = $post->author;
# 支持一次加载多个关联模型(参数为对应关联方法名
$posts = Post::with('author', 'comments', 'tags')->findOrFail(1);
# 支持嵌套查询
$post = Post::with('author.profile')->findOrFail(1);
# 指定要加载的字段
$post = Post::with('author:id,name')->findOrFail(1);
# 通过闭包传入额外的约束条件
$post = Post::with(['comments' => function ($query) {
$query->where('content', 'like', '空空%')
->orderBy('created_at', 'desc');
}])->where('id', '<', 5)->get();
懒惰渴求式加载
通过动态条件判断进行渴求式加载或者延迟加载,通过 load
方法实现。
$users = User::all();
$condition = true;
if ($condition) {
$users->load('posts');
}
# 支持通过闭包传递额外的约束条件
$posts = Post::where('id', '<=', 3)->get();
$posts->load(['comments' => function ($query) {
$query->where('content', 'like', 'Laravel学院%')
->orderBy('created_at', 'desc');
}]);
异步分页
结合 Bootstrap 和 Vue 组件实现异步分页功能,需要在后端定义好分页数据获取 API 接口。
# 定义后端 API 接口
# 1.在 PostController 中定义一个 fetch 方法用于异步获取分页数据
# 2.在 routes/api/php 中定义指向该控制器方法的 API 路由
# 测试API 接口,在浏览器中请求 http://blog.test/api/posts/fetch
# 3.在控制器的文章首页列表方法 index 中,返回一个视图用于渲染文章列表
# 创建文章列表视图
# 4.新建 resources/views/post/index.blade.php,编写视图代码
# 创建 Vue 分页组件
# 5.新建 resources/js/components/PaginationComponent.vue,初始化代码并在 resources/js/app.js 中注册组件
# 6. npm run dev编译前端资源