Laravel基础
Laravel 基础
01.Laravel入门和安装
Composer安装Laravel步骤
要使用 Composer 安装 Laravel,请按照以下步骤操作:
-
确保已经安装了 Composer。如果还没有安装,请访问 https://getcomposer.org/download/ 下载并安装。
-
打开命令行或终端。
-
使用
cd
命令导航到你的项目目录,例如:cd /path/to/your/project
-
在项目目录中,运行以下命令创建一个新的Laravel项目:
composer create-project --prefer-dist laravel/laravel your_project_name
其中,
your_project_name
是你的项目名称。这将下载 Laravel 的最新版本并创建一个新的项目。 -
安装完成之后进入项目
cd your_project_name
-
将
.env.example
文件复制并重命名为.env
:cp .env.example .env
-
在
.env
文件中配置数据库连接信息。例如:DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=your_database_name DB_USERNAME=your_database_username DB_PASSWORD=your_database_password
-
生成应用密钥:
php artisan key:generate
-
运行数据库迁移和数据填充(如果有):
php artisan migrate php artisan db:seed
-
最后,启动开发服务器:
php artisan serve
这将在本地启动一个 HTTP 服务器,通常在 http://localhost:8000
上运行。现在你可以开始构建你的 Laravel 应用了。
02.路由的定义和控制器
一.路由的定义
-
什么是路由?路由就是提供接受 HTTP 请求的路径,并和程序交互的功能;
-
简单点理解,就是为了提供访问程序的 URL 地址,所做的一些设置工作;
-
phpstorm 支持 cmd 操作,左下角 Terminal 按钮展开,调整字体即可;
-
输入 php artisan serve 命令后,即支持 localhost:8000 内置服务器;
-
路由的定义文件在根目录 routes/web.php 中,可以看到welcome 页面;
-
我们创建一个路由,让它返回一段信息,并设置响应的 url 地址;
Route::get('index', function () {return "<h1>Hello World</h1>"; });
-
在路由定义上,我们采用了::get()这个方法,它接受的就是GET 提交;
-
::post()、::put()、::delete()是表单和 Ajax 的提交接受方式;
-
::any()表示不管你是哪种提交方式,我智能的全部接收响应;
-
::match()表示接收你指定的提交方式,用数组作为参数传递;
// 使用 match 必须有三个参数, any 是两个参数 Route::match(['get', 'post'], 'match', function () {return "Matched"; });
-
在路由的规则和闭包区域,我们可以设置和传递路由参数;
Route::get('index/{id}', function ($id) {return "<h1>Hello World $id</h1>"; }); // http://localhost:8000/index/5
-
上面例子中{id}表示在 url 的动态参数,比如数字 5;
-
那么闭包的$id,可以接受 url 传递过来的 5,最终输出 5;
二.创建控制器
-
MVC 模式中 C 代表控制器,用于接收 HTTP 请求,从而进行逻辑处理;
-
有两种方式可以创建控制器,IDE 直接创建,或使用命令生成一个;
php artisan make:controller TaskController
-
控制器目录在 app\Http\Controllers 下,使用 IED 会自动生成命名空间;
<?php namespace App\Http\Controllers;class TaskConterollers extends Controller {public function index(){return "task index";}public function read($id){return "id is $id";} }
-
通过设置路由来访问创建好的控制器,参数二:控制器@方法名;
use App\Http\Controllers\TaskConterollers;Route::get("/task/read/{id}", [TaskConterollers::class, 'read'])->where(['id' => '[0-9]+', 'name' => '[a-z]+']);
03.路由参数.重定向.视图
一.路由参数
-
上一节课,我们已经学习了部分路由参数的功能,比如动态传递
{id}
; -
那么,有时这个参数需要进行约束,我们可以使用正则来限定必须是数字;
//单个参数 Route::get("/task/read/{id}", [TaskConterollers::class, 'read'])->where('id', '[0-9]+');// 多个参数 Route::get("/task/read/{id}", [TaskConterollers::class, 'read'])->where(['id' => '[0-9]+', 'name' => '[a-z]+']);
-
如果想让约束 id 只能是 0-9 之间作用域全局范围,可以在模型绑定器里设置;
-
模型绑定器路径为:
app\Providers\RouteServiceProvider 的boot()
方法;public function boot() {$this->pattern('id', '[0-9]+'); // 全局都有效 }
-
如果 id 已经被全局约束,在某个局部你想让它脱离约束,可以如下操作:
// 全局范围的约束解除 Route::get("/task/read/{id}", [TaskConterollers::class, 'read'])->where('id', '.*');
二.路由重定向
-
可以设置访问一个路由的 URI,跳转到另一个路由的 URI,具体如下:
Route::redirect('index', 'task'); Route::redirect('index', 'task', 301); // 状态码
-
还有一个方法,可以直接让路由跳转返回 301 状态码而不用设置:
Route::permanentRedirect('index', 'task');
三.视图路由
-
在使用视图路由之前,我们先要创建一个视图(MVC)中的 V 部分;
-
使用视图路由,有三个参数:1.URI(必);2.名称(必);3.参数(选);
//参数 1:URI,localhost:8000/task //参数 2:view,resources/views/task.blade.php //参数 3:传参,{{$id}} Route::view('task', 'task', ['id'=>10]);
-
对于视图页面的创建,在 resources/views 下创建 task.blade.php;
{{--静态页面--}} task{{$id}}
-
当然,也可以使用助手函数 view()方法来实现这个功能;
//这里 view()的参数 1 是视图名称 //参数 2 传参,可选; Route::get('task', function () {return view('task', ['id'=>10]); });
-
也可以将路由直接指向控制器的方法,通过方法实现 view()引入视图;
public function index() {return view('task', ['id'=>10]); }
04.路由命名和分组
一.路由命名
-
给一个制定好的路由进行命名,可以生成 URL 地址或进行重定向;
Route::get('task', 'TaskController@index')->name('task.index');
-
在控制器区域,使用助手函数 route()来获取路由生成的 URL 地址;
//生成 url 地址,http://localhost:8000/task route('task.index'); PS:URL 是 URI 的子集,更多区别请百度;
-
route()
助手的第二参数为参数,第三参数为是否包含域名URL;// http://localhost:8000/task?id=10 route('task.index', ['id'=>10]); // /task?id=10 $url = route('task.index', ['id'=>10], false); // PS:如果需要更改成/task/10 模式,路由需要相应更改 task/{id}
-
使用 redirect()助手结合 route()生成一个重定向跳转,注意不要自我死跳;
//生成重定向 return redirect()->route('task.index', ['id'=>10]);
二.路由分组
-
路由分组功能是为了让大量路由共享路由属性,包括中间件、命名空间等;
//一个空的分组路由 Route::group([], function () {Route::get('index/{id}', function ($id) {return 'index'.$id;});Route::get('task/{id}', function ($id) {return 'task'.$id;}); });
-
可以将中间件作用域路由分组中,有两种写法,至于中间件?后续章节讲解;
//引入中间件,方法一 Route::group(['middleware'=>'中间名'], function () {}); //引入中间件,方法二 Route::middleware(['中间件'])->group(function () {});
-
可以设置路由路径前缀,通过 prefix 来设置,也有两种方法,具体如下:
//引入路由前缀,方法一 Route::group(['prefix'=>'api'],function () {}); //引入路由前缀,方法二 Route::prefix('api')->group(function () {});
-
可以设置子域名,从而限定路由可执行的域名,有两种方法,具体如下:
//引入子域名,方法一 Route::group(['domain'=>'127.0.0.1'], function () {}); //引入子域名,方法二 Route::domain('127.0.0.1')->group(function () {});
-
可以设置命名空间,让命名空间分配给控制器,让其得以访问,具体如下:
//命名空间,方法一 Route::group(['namespace'=>'Admin'],function () {}); //命名空间,方法二 Route::namespace('Admin')->group(function () {}); PS:在 Controller 目录下创建 Admin 目录,再其目录下创建的控制器命名空间如下:namespace App\Http\Controllers\Admin;
-
可以设置名称前缀,方式两种,也可以嵌套,具体如下:
//名称前缀,方式一 Route::group(['as'=>'task.'], function () {Route::get('task', 'TaskController@index')->name('index');Route::get('task/url', 'TaskController@url'); }); //名称前缀,方式二 Route::name('task.')->group( function () {}); //生成 URL $url = route('task.index'); return $url; //嵌套方式命名前缀 Route::name('task.')->group(function () {Route::name('abc.')->group(function () {Route::get('task', 'TaskController@index')->name('index');});Route::get('task/url', 'TaskController@url'); }); //生成 URL $url = route('task.abc.index'); return $url;
05.回退.当前路由.单行为
一.单行为控制器
-
之前的课程,我们简单的创建和定义了控制器,并继承了控制器基类;
-
为何要继承基类?因为继承基类后,可以使用基类的方法,比如中间件等;
-
继承基类后除了支持中间件快捷使用,还支持验证、列队等快捷方法;
public function __construct() {$this->middleware('中间件'); }
-
如果你想要定义一个只执行一个方法的控制器,可以使用单行为控制器;
-
单行为控制器使用
__invoke()
方法,可以使用命令行创建;命令:
php artisan make:controller OneController --invokable
//手工创建 class OneController extends Controller {public function __invoke(){return '单行为控制器';} }
-
单行为控制器,路由定义就不需要指定特定的方法,指定控制器即可
-
单行为控制器只是语义上的单行为,并没有限制创建更多方法访问;
Route::get('one', 'OneController');
二.路由回退
-
如果我们跳转到了一个不存在路由时,会产生 404 错误,体验不佳;
-
可以使用回退路由,让不存在的路由自动跳转到你指定的页面去;
-
注意:由于执行顺序问题,必须把回退路由放在所有路由的最底部;
Route::fallback(function () {return redirect('/'); });
-
当然,你也可以制作一个自己的 404 页面,用回退路由加载这个页面;
Route::fallback(function () {return view('404'); });
三.当前路由
-
我们可以通过使用
::current()
系列方法,来获取当前路由的访问信息;Route::get('index', function () {//当前路由信息dump(Route::current());//返回当前路由的名称return Route::currentRouteName();//返回当前路由指向的方法return Route::currentRouteAction(); })->name('localhost.index');
06.响应设置和重定向
一.响应设置
-
路由和控制器处理完业务都会返回一个发送到浏览器的响应:return;
-
比如字符串会直接输出,而数组则会输出 json 格式,本身是Response 对象;
return [1, 2, 3]; //输出 json 格式 return response([1, 2, 3]); //同上 return response()->json([1, 2, 3]); //同上
-
如果使用
response()
输出的话,可以设置状态码和响应头信息;return response('index', 201); //可以设置 HTTP 请求状态码
-
也可以给 HTTP 添加或修改标头,比如将 html 解析模式改成文本plain 模式;
return response('<b>index</b>')->header('Content-Type', 'text/plain'); //文本解析模式
-
结合上面的响应操作,再结合 view()视图功能,显示纯 HTML 代码页面;
return response()->view('task', ['id'=>10], 201)->header('Content-Type', 'text/plain');
二.路由重定向
-
重定向使用助手函数 redirect()的 to()方法,注意需要return 才能跳转
return redirect()->to('/'); //跳到首页 return redirect()->to('task'); //跳转到 task return redirect()->to('task/url'); //跳转到 task/url
-
也可以直接使用快捷方式直接进行跳转; 常用
return redirect('/'); //跳到首页
return redirect('task'); //跳转到 task
return redirect('task/url'); //跳转到 task/url
-
redirect()
助手有一个对应的 facade 模式对象;return Redirect::to('/'); //facade 模式,但需要 use 引入
-
使用
redirect()
的 route()方法,可以跳转到指定的命名路由URI;return redirect()->route('task.index'); //注意和 route()方法区别
-
使用
redirect()
的back()
方法,可以重定向到上一个页面中;常用return redirect()->back(); return back(); //快捷方式
-
使用
redirect()
的 action()方法,可以直接重定向到控制器方法;return redirect()->action('TaskController@index'); //需注册路由 return redirect()->action('TaskController@index', ['id'=>10]);
-
使用
redirect()
的 away()方法,跳转到外部链接;return redirect()->away('http://www.baidu.com'); //不带任何编码
07.资源控制器
一.资源控制器
-
声明:资源控制器是某个特定场景下的产物,完全理解需要PHP项目基础;
-
比如开发过博客系统,留言帖子系统之类,具有类似思维,否则你懂的…;
-
只是学习了PHP基础,就立刻学习框架的同学,可以过一遍即可(不影响后续)…;
-
有一种控制器专门处理CURD(增删改查),方法很多且方法名基本固定;
-
对于这种控制器,我们可以将它设置为资源型控制器,不要大量设置路由;
-
这里推荐直接使用命令行生成资源路由,比如:
BlogController
php artisan make:controller BlogController --resource
-
生成了的资源控制器会产生 7 个方法,配置好路由后会自动生成相关内容
Route::resource('blogs', BlogController::class); //单个资源路由//批量定义资源路由 // 多个路由 Route::resources(["blogs" => BlogController::class,"tasks" => TaskController::class ]);
HTTP类型 路由URI 控制器方法 路由命名 描述 GET blogs index() blogs.index 获得数据列表 GET blogs/create create() blogs.create 创建页面(表单页) POST blogs store() blogs.store 创建页的接受处理 GET blogs/{blog} show() blogs.show 获得一条数据 GET blogs/{blog}/edit edit() blogs.edit 编辑(表单页) PUT/PATCH blogs/{blog} update() blogs.update 从编辑页中接受处理 DELETE blogs/{blog} destroy() blogs.destroy 删除一条数据 -
如果我们注册了资源路由,那么如上图的资源路由 URI 和名称均自动创建生效;
http://localhost:8000/blogs/10/edit //可以访问到edit方法return route('blogs.store'); //可以通过助手route()了解是否注册
-
还有一条命令可以直接查看目前可用的路由以及命名; 常用
php artisan route:list
-
我们也可以限制资源路由只开放部分方法或排除部分方法,可以用命令查看;
// 只有 index 和 show 可以访问 Route::resource('blogs', BlogController::class)->only(['index', 'show']); // index 和 show 不可访问,剩下的都可以 Route::resource('blogs', BlogController::class)->except(['index', 'show']);
-
资源控制器还有一种不需要HTML页面方法的API路由,只提供数据接口;
//API资源,并不需要HTML页面(create,edit),会排除 Route::apiResource('blogs', 'BlogController');//批量方式 Route::apiResources(['blogs' => 'BlogController' ]);
-
当然,也支持一开始就生成一个不包含HTML页面方法的资源控制器;
-
要注意的是,对应的资源路由,直接使用api资源路由即可;创建时就说是API
php artisan make:controller CommentController --api
Route::apiResource('comments', CommentController::class);
08.资源嵌套.浅嵌套.自定义
-
声明:资源控制器是某个特定场景下的产物,完全理解需要PHP项目基础;
-
比如开发过博客系统、留言帖子系统之类,具有类似思维,否则你懂的…;
-
只是学习了PHP基础就立刻学习框架的同学,可以过一遍即可(不影响后续);
-
在一篇博文(Blog)下有多条评论(Comment),编辑某条博文下的一条评论;
-
以上需求,可以通过嵌套资源路由来实现这个功能:
php artisan make:controller CommentController --resource
//嵌套资源路由 Route::resource('blogs.comments', CommentController::class);
HTTP类型 路由URI 控制器方法 路由命名 GET blogs/{blog}/comments
index()
blogs.comments.index
GET blogs/{blog}/comments/create
create()
blogs.comments.create
POST blogs/{blog}/comments
store()
blogs.comments.store
GET blogs/{blog}/comments/{comment}
show()
blogs.comments.show
GET blogs/{blog}/comments/{comment}/edit
edit()
blogs.comments.edit
PUT/PATCH blogs/{blog}/comments/{comment}
update()
blogs.comments.update
DELETE blogs/{blog}/comments/{comment}
destroy()
blogs.comments.destroy
-
以上需求,可以通过嵌套资源路由来实现这个功能,编辑方法以及传参如下:
public function edit($blog_id, $comment_id) {return '编辑博文下的评论,博文id: ' . $blog_id . ',评论id: ' . $comment_id; }
-
而实际上,每个 id 都是独立唯一的,并不需要父 id 和子id 同时存在;
-
为了优化资源嵌套,通过路由方法
->shallow()
实现浅层嵌套方法; 取消父级// 浅层嵌套 Route::resource('blogs.comments', 'CommentController')->shallow();
-
实现后的路由及参数传递更精准:
HTTP类型 路由URI 控制器方法 路由命名 GET blogs/{blog}/comments
index()
blogs.comments.index
GET blogs/{blog}/comments/create
create()
blogs.comments.create
POST blogs/{blog}/comments
store()
blogs.comments.store
GET comments/{comment}
show()
blogs.comments.show
GET comments/{comment}/edit
edit()
blogs.comments.edit
PUT/PATCH comments/{comment}
update()
blogs.comments.update
DELETE comments/{comment}
destroy()
blogs.comments.destroy
public function edit($id) {return '评论id: ' . $id; }
-
如果觉得资源路由命名过长,可以自己自定义,有两种方式:
->name('index', 'b.c.i'); ->names(['index' => 'b.c.i']);
-
如果觉得资源路由的参数不符合你的心意,也可以改变:
->parameter('blogs', 'id'); ->parameters(['blogs' => 'blog_id', 'comments' => 'comment_id' ]);
09.表单伪造和CSRF保护
一.表单伪造
-
之前一直用的 GET 请求方式,而表单可以实现 POST 方式,我们来实验下:
-
先在 TaskController 创建两个方法,一个表单页,一个接受表单数据路由;
public function form() {return view('form'); } //表单页 Route::get('task/form', 'TaskController@form'); //接受表单数据 Route::any('task/getform', function () {return \Illuminate\Support\Facades\Request::method(); });
-
表单页以 post 发送,路由也使用 post 接受,以下表单提交会出现419 错误;
<form action="/task/getform" method="post">用户名:<input type="text" name="user"><button type="submit">提交</button> </form>
-
这是为了避免被跨站请求伪造攻击,框架提供了 CSRF 令牌保护,请求时验证;
<input type="hidden" name="_token" value="{{csrf_token()}}">
-
表单可以实现 POST 提交方式,那其它提交方式该如何实现呢?可以采用伪造技术;
<input type="hidden" name="_method" value="PUT">
-
对于 CSRF 令牌保护和表单伪造提交方式,也支持快捷方式的声明,如下:
@csrf @method('PUT')
-
如果我们想让某些 URL 关闭 csrf 验证,可以设置 csrf 白名单;
-
白名单具体设置位置在:中间件目录下的 VerifyCsrfToken.php 文件;
-
当然,不建议直接注释掉这个验证 csrf 功能的中间件;
protected $except = ['api/*', ];
10.数据库配置入门
一.配置数据库
-
框架支持原生、查询构造器和 Eloquent ORM(关系型对象映射器)来操作数据库;
-
数据库的配置在
config/database.php
,如果是本地可以直接配置.env 文件; -
我们通过.env 文件配置数据库连接的相关信息,以提供给database 读取;
DB_CONNECTION=mysql //.env DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=grade DB_USERNAME=root DB_PASSWORD=123456 'mysql' => [ //.database.php'driver' => 'mysql',... ]
-
我们可以直接创建一个新的控制器 DataController 来测试数据库部分;
-
数据库有一个专用类 DB,可以用它来实现原生查询和构造器查询;
//使用 DB 类的 select()方法执行原生 SQL $user = DB::select('select * from laravel_user'); return $user;
-
查询构造器主要通过 DB 类的各种数据库操作方法来实现,比如选定一条;
//这里省去了 laravel_,需要在 database.php 配置 $user = DB::table('user')->find(19); return [$user];
-
由于火狐浏览器自动将 JSON 显示的很美化,而 find()只返回对象;
return response()->json($user);
-
使用 Eloquent ORM 模型来操作数据库,使用命令在 Http 目录下创建模型;
php artisan make:model Http/Models/User
//使用 Eloquent ORM 构建 $user = User::all(); return $user;
上面使用模型来操作数据后,报错提示数据表是复数:users;
而我们真实的数据库表为:laravel_user,为何会这样???
前缀可以在 database.php 修改添加:laravel_,最终变为:laravel_users;
由于模型编码规范要求数据表是复数,这里的复数并不是单纯加s;
可能会加 es,可能会加 ies,也可能是 child 编程 children 之类的;
可以使用字符串助手:Str::plural()来判断英文单词的复数情况:
return Str::plural('bus'); //buses
return Str::plural('user'); //users
return Str::plural('child'); //children
你可以根据规范去更改数据表名称,或者强制使用现有的数据表名;
protected $table = 'user';
11.构造器的查询.分块.聚合
一.构造器查询
-
table()方法引入相应的表,get()方法可以查询当前表的所有数据;
//获取全部结果 $users = DB::table('users')->get();
-
first()方法,可以获取到第一条数据;
//获取第一条数据 $users = DB::table('users')->first();
-
value(字段名)方法,可以获取到第一条数据的指定字段的值;
//获取第一条数据的 email 字段值 $users = DB::table('users')->value('email');
-
find(id)方法,可以获取指定 id 的一条数据;
//通过 id 获取指定一条数据 $users = DB::table('users')->find(20);
-
pluck(字段名)可以获取所有数据单列值的集合;
//获取单列值的集合 $users = DB::table('users')->pluck('username'); $users = DB::table('users')->pluck('username', 'email');
二.分块.聚合
-
如果你一次性处理成千上万条记录,防止读取出错,可以使用chunk()方法;
//切割分块执行,每次读取 3 条,id 排序; DB::table('users')->orderBy('id')->chunk(3, function ($users) {foreach ($users as $user) {echo $user->username;}echo '------<br>'; });
-
构造器查询提供了:count()、max()、min()、avg()和 sum()聚合查询;
//聚合查询 return DB::table('users')->count(); return DB::table('users')->max('price'); return DB::table('users')->avg('price');
-
构造器查询两个判断记录是否存在的方法:exists()和 doesntexists()方法;
//判断是否存在 return DB::table('users')->where('id', 19)->exists(); return DB::table('users')->where('id', 18)->doesntExist();
PS:这里 DB::第一个使用静态,返回查询对象,然后使用->where 等各种查询方法,这些查询方法返回的还是查询对象,所以可以继续连缀操作。最后当遇到比如get()返回结果等方法时,停止连缀。所以,返回结果必须放在最后。
12.构造器的查询表达式
一.select 查询
-
select()
方法可以制定你想要的列,而不是所有列;//设置显示的列,设置列别名 $users = DB::table('users')->select('username as name', 'email')->get();
-
addSelect()
方法,可以在你基础的查询构造器上再增加想要显示的字段;//给已经构建好的查询添加更多字段 $base = DB::table('users')->select('username as name', 'email'); $users = $base->addSelect('gender')->get();
-
DB::raw()方法可以在 select()内部实现原生表达式,否则解析错误;
//结合原生 SQL 实现复杂查询 $users = DB::table('users')->select(DB::raw('COUNT(*) AS id, gender'))->groupBy('gender')->get();
-
也可以直接使用 selectRaw()方法实现内部原生;
//或者直接使用 selectRaw()方法实现原生 $users = DB::table('users')->selectRaw('COUNT(*) AS count, gender')->groupBy('gender')->get();
-
还可以通过 havingRaw()方法实现更精准的分组筛选;
//使用 havingRaw 方法实现分组筛选 $users = DB::table('users')->selectRaw('COUNT(*) AS count, gender')->groupBy('gender')->havingRaw('count>5')->get();
二.where 查询
-
where()查询,即条件查询,完整形式需要字段表达式和值三个;
//where 查询完整形式 $users = DB::table('users')->where('id', '=', 19)->get();
-
大部分情况下,是等于用的比较多,就可以省略掉=号参数;
//where 查询完整形式 $users = DB::table('users')->where('id', 19)->get();
-
当然,还有>、<、>=、<=、<>、like 等操作符;
users = DB::table('users')->where('price', '>=', 95)->get(); $users = DB::table('users')->where('username', 'like', '%小%')->get();
-
如果条件较多,可以用数组来分别添加条件,具体如下:
//如果条件都是等于,查看 SQL 语句用->toSql()替换->get() $users = DB::table('users')->where(['price' => 90,'gender' => '男' ])->get(); //如果条件非等于 $users = DB::table('users')->where([['price', '>=', 90],['gender', '=', '男'] ])->get();
13.构造器的where派生查询
一.where 派生查询
-
orWhere()方法,可以通过连缀实现两个或以上的 or 条件查询;
//where() + orWhere 实现 or 条件查询 $users = DB::table('users')->where('price', '>', 95)->orWhere('gender', '女')->toSql();
-
通过闭包,我们还可以构建更加复杂的 orWhere 查询;
//orWhere()结合闭包查询 $users = DB::table('users') ->where('price', '>', '95') ->orWhere(function ($query) {$query->where('gender', '女')->where('username', 'like', '%小%'); })->toSql();
-
whereBetween()可以实现区间查询,比如价格在一个区间内的用户;
//whereBetween 查询区间价格 60~90 之间 $users = DB::table('users')->whereBetween('price', [60, 90])->toSql(); // PS:这里还支持相关三种:whereNotBetween/orWhereBetween/orWhereNotBetween;
-
whereIn()可以实现数组匹配查询,比如匹配出数组里指定的数据;
//whereIn 查询数组里匹配的数值 $users = DB::table('users')->whereIn('id', [20,30,50])->toSql();
-
whereNull()可以查询字段为 Null 的记录;
//whereNull 查询字段值为 Null 的记录 $users = DB::table('users')->whereNull('uid')->toSql();
PS:这里还支持相关三种:whereNotNull/orWhereNull/orWhereNotNull;
-
whereDate()可以查询指定日期的记录;
//whereYear 查询指定日期的记录,或大于 $users = DB::table('users')->whereDate('create_time', '2018-12-11')->toSql();
// PS:这里还支持相关四种:
whereYear/whereMonth/whereDay/whereTime
,支持or 前缀;
// PS:三个参数支持大于小于之类的操作 orWhereDate(‘create_time’,‘>’, ‘2018-12-11’)
14.构造器的排序分组.子查询
一.排序分组
-
使用
whereColumn()
方法实现两个字段相等的查询结果;//判断两个相等的字段,同样支持 orWhereColumn() //支持符号'create_time','>', 'update_time' //支持符号支持数组多个字段格式['create_time','>', 'update_time'] $users = DB::table('users')->whereColumn('create_time', 'update_time')->get();
-
使用
orderBy()
方法实现 desc 或 asc 排序功能。//支持 orderByRaw 和 orderByDesc 倒序方法 $users = DB::table('users')->orderBy('id', 'desc')->get();
-
使用
latest()
方法设置时间倒序来排,默认时间字段是 created_at;//按照创建时间倒序排,默认字段 created_at $users = DB::table('users')->latest('create_time')->toSql();
-
使用
inRandomOrder()
方法来随机排序,得到一个随机列表;//随机排序 $users = DB::table('users')->inRandomOrder()->get();
-
使用 skip()和 take()限制结果集,或使用 offset()和 limit();
//从第 3 条开始,显示 3 条 $users = DB::table('users')->skip(2)->take(3)->toSql(); $users = DB::table('users')->offset(2)->limit(3)->get();
-
使用 when()方法可以设置条件选择,执行相应的 SQL 语句;
//when 实现条件选择 $users = DB::table('users')->when(true, function ($query) {$query->where('id', 19); }, function ($query) {$query->where('username', '辉夜'); })->get();
-
如果 MySQL 在 5.7+,有支持 JSON 数据的新特性;
$users = DB::table('users')->where('list->id', 19)->first();
二.子查询
-
使用
whereExists()
方法实现一个子查询结果,返回相应的主查询;//通过 books 表数据,查询到 users 表关联的所有用户 $users = DB::table('users')->whereExists(function ($query) {$query->selectRaw(1)->from('books')->whereRaw('laravel_books.user_id = laravel_users.id'); })->toSql();// whereRaw 这句也可以替代为:whereColumn('books.user_id','users.id'); // PS:select 1 from,一般用于子查询的手段,目的是减少开销,提升效率,深入请搜索;
-
也可以使用 where(字段,function())闭包,来实现一个子查询;
//id=子查询返回的 user_id $users = DB::table('users')->where('id', function ($query) {$query->select('user_id')->from('books')->whereColumn('books.user_id','users.id'); })->toSql();
15.构造器的join查询
一.join 查询
-
使用 join 实现内联接的多表查询,比如三张表进行 inner join 查询;
$users = DB::table('users')->join('books', 'users.id', '=', 'books.user_id')->join('profiles', 'users.id', '=', 'profiles.user_id')->select('users.id', 'users.username', 'users.email','books.title', 'profiles.hobby')->get();
-
也可以使用 leftjoin 左连接或 rightjoin 右连接实现多表查询;
$users = DB::table('users')->leftJoin('books', 'users.id', '=', 'books.user_id')->rightjoin('profiles', 'users.id', '=', 'profiles.user_id')->get();
-
使用 crossjoin 交叉连接查询,会生成笛卡尔积,再用 distinct()取消重复;
$users = DB::table('users')->crossJoin('books')->select('username', 'email')->distinct()->get();
-
如果你想要实现闭包查询,和 where 类似,只不过要用 on 和orOn 方法;
$users = DB::table('users')->join('books', function ($join) {//支持 orOn 连缀$join->on('users.id', '=', 'books.user_id');})->toSql(); // PS:on()方法后面如果想要再增加筛选条件,可以追加 where();
-
使用 joinSub 实现子连接查询,将对应的内容合并在一起输出;
//子连接查询 $query = DB::table('books')->selectRaw('user_id,title'); $users = DB::table('users')->joinSub($query,'books', function ($join) {$join->on('users.id', '=', 'books.user_id'); })->get();
-
使用 union()或 unionAll()方法实现两个查询的合并操作;
//union 取消重复,unionAll 不取消重复 $query = DB::table('users'); $users = DB::table('users')->union($query)->get();
16.构造器的增删改
一.增删改操作
-
使用 insert()方法可以新增一条或多条记录;
//新增一条记录 DB::table('users')->insert(['username' => '李白','password' => '123456','email' => 'libai@163.com','details' => '123' ]); //新增多条记录 DB::table('users')->insert([[...],[...] ]);
-
使用 insertOrIgnore()方法,可以忽略重复插入数据的错误;
//忽略重复新增数据的错误 DB::table('users')->insertOrIgnore(['id' => 304,'username' => '李白','password' => '123456','email' => 'libai@163.com','details' => '123' ]);
-
使用 insertGetId()方法,获取新增后的自增 ID;
//获取新增后返回的 ID $id = DB::table('users')->insertGetId(['username' => '李白','password' => '123456','email' => 'libai@163.com','details' => '123' ]); return $id;
-
使用 update()方法,可以通过条件更新一条数据内容;
//更新修改一条数据 DB::table('users')->where('id', 304)->update(['username' => '李红','email' => 'lihong@163.com']);
-
使用 updateOrInsert()方法,可以先进行查找修改,如不存在,则新增;
//参数 1:修改的条件 //参数 2:修改的内容(新增的内容) DB::table('users')->updateOrInsert(['id'=>307],['username'=>'李黑', 'password'=>'654321', 'details'=>'123'] );
-
对于 json 数据,新增和修改的方法和正常数据类似;
//新增时,转换为 json 数据 'list' => json_encode(['id'=>19]) //修改时,使用 list->id 指定 DB::table('users')->where('id', 306)->update(['list->id' => 20]);
-
更新数据时,可以使用自增 increment()和自减 decrement()方法;
//默认自增/自减为 1,可设置 DB::table('users')->where('id', 306)->increment('price'); DB::table('users')->where('id', 306)->increment('price', 2);
-
使用 delete()删除数据,一般来说要加上 where 条件,否则清空;
//删除一条数据 DB::table('users')->delete(307); DB::table('users')->where('id', 307)->delete(); //清空 DB::table('users')->delete(); DB::table('users')->truncate();
17.模型的定义
一.默认设置
-
框架可以使用 Eloquent ORM 进行数据库交互,也就是关系对象模型;
-
在数据库入门阶段,我们已经创建了一个 User.php 模型,如下:
php artisan make:model Http/Models/User
默认在 app 目录 -
而调用的时候,我们也知道表名要遵循它默认规则,修改为复数,或特定;
class User extends Model {protected $table = 'user'; }
-
系统假定你的主键为 id,如果你要修改默认主键,可以特定;
protected $primaryKey = 'xid';
-
系统假定你的主键 id 为自增性,意味着是主键会自动转换int 类型;
-
如果你希望不是非自增,非数值类型主键,可以设置取消;
public $incrementing = false;
-
如果你主键不是一个整数,那么需要
$keyType
设置为 string;protected $keyType = 'string';
-
系统默认情况下会接管 created_at 和 updated_at 两个时间戳列;
-
如果不想让系统干涉这两个列,可以设置 false 取消;
public $timestamps = false;
-
如果你想自定义时间戳的格式,可以设置;
protected $dateFormat = 'U';
-
可以更改创建时间 created_at 和更新时间 updated_at 字段名;
const CREATED_AT = 'create_time'; const UPDATED_AT = 'update_time';
-
默认读取
database.php
配置的数据库连接,也可以在模型端局部更改;protected $connection = 'mysql';
二.模型定义
-
之前在查询构造器部分,把常用的数据库操作基本讲完,模型大体相同;
-
比如,我们要查询所有数据,直接使用模型::all()即可;
//查询所有记录 $users = User::get(); //或 all() return [$users];
-
也可以像查询构造器一样,添加各种各样的条件,写法一样;
//查询性别为男,价格大于 90,限制显示 2 条 $users = User::where([['gender', '=', '男'],['price', '>', 95] ])->limit(2)->get();
-
虽然安装了插件,但模型还是没有代码提示,可以通过安装插件解决;
composer require barryvdh/laravel-ide-helperphp artisan ide-helper:generate – 为 Facades 生成注释 php artisan ide-helper:models – 为数据模型生成注释 php artisan ide-helper:meta – 生成 PhpStorm Meta file
-
其它查询方法基本和查询构造器一样,如果有不一样,参考错误提示;
-
这里列出官网给出示例的方法,对照实验(结合详细文档,重复较多);
(1) .find(1) //通过主键查找 (2) .first() //查找第一个 (3) .firstWhere() //找到查询中的首个 (4) .find([1,2,3]) //通过数组查找 (5) .firstOr() //查找首个返回,支持闭包 (6) .firstOrFail() //找不到时返回异常 (7) .count()、max()等集合 //集合操作
PS:还有很多在查询构造器中的方法,比如排序、分组子查询等等都可以使用(并未一一验证)。
18.模型的增删改
一.增删改操作
-
新增方法如下,注意:默认模型接管 created_at 和 updated_at;
$users = new User(); $users->username = '辉夜'; $users->password = '123'; $users->email = 'huiye@163.com'; $users->details = '123'; $users->save();
-
更新,只要是查找到一条数据的情况下使用 save()就是更新;
$users = User::find(321); $users->username = '夜辉'; $users->save();
-
使用 update()方法实现批量更新;
User::where('username', '夜辉')->update(['username' => '辉夜']);
-
使用 create()方法实现新增,但需要在模型端设置批量赋值的许可;
User::create(['username' => '辉夜','password' => '123','email' => 'huiye@163.com','details' => '123', ]); //许可批量赋值,默认不可 protected $fillable = ['username','password','email','details' ]; //不许可的批量赋值,不可和$fillable 同时使用 //protected $guarded = ['uid']; //如果取消批量赋值限制,直接如下 protected $guarded = [];
PS:必须在模型中定义批量赋值的可填充字段,否则无法生效;防止用户不小心设置新值;
-
使用 delete()方法,可以删除数据;
$users = User::find(332); $users->delete(); //批量删除 $users = User::where('username', '夜辉'); $users->delete();
-
如果你是通过主键 id 删除,那使用 destroy(id)方法,免去查询操作;
//通过主键删除 User::destroy(328);
19.批量赋值和软删除
一.批量赋值
- 上一节增删改中,新增中我们发现需要进行批量赋值的许可;
- 一般情况下,是为了防止提交过来的字段在部分场景中不需要或不能;
- 所以,我们需要通过黑白名单机制进行过滤掉必要的字段;
//通过提交过来的数据一次性新增
User::create(\Request::all());
二.软删除
-
什么叫软删除?它相对于真实的删除,而并非真正的删除,只是隐藏了;
-
首先,需要在数据库创建一个字段 deleted_at(默认),用于判断是否被软删除;
-
默认设置这个字段为空(null),如果写入数据,成为非空状态,则说明被删除;
-
开启软删除的功能,需要在模型端设置一下:
use SoftDeletes; //开启软删除功能
-
当开启了软删除功能,之前的删除操作,都会变成更新操作,给deleted_at 赋值;
//删除一 $users = User::find(82); $users->delete(); //删除二 User::destroy(81);
-
而当我们开启了软删除的功能后,此时通过正常的数据获取列表,会自动隐藏;
//软删除的数据不可见 $users = User::get(); return [$users]; //单独获取被软删除的数据不行 $users = User::find(82); return [$users];
-
如果需要查询包含软删除的数据,通过
withTrashed()
方法实现;//获取包含软删除的数据 $users = User::withTrashed()->get(); return [$users]; //获取某个被软删除的数据(即使不是软删除的也可以搜索到) $users = User::withTrashed()->find(82); return [$users];
-
如果只想搜索出被软删除的数据,通过 onlyTrashed()方法实现;
//获取所有软删除的数据 $users = User::onlyTrashed()->get(); return [$users]; //获取某个被软删除的数据(只有软删除的数据才可以被搜索到) $users = User::onlyTrashed()->find(82); return [$users];
-
如果要对这个数据进行软删除的判断,是否是被软删除的,可以使用trashed();
//判断是否是被软删除的数据 $users = User::withTrashed()->find(81); return $users->trashed();
-
如果想将软删除的数据恢复正常(类似从回收站还原),使用restore()方法;
//将被软删除的数据回复正常 $users = User::onlyTrashed()->find(82); $users->restore();
-
如果开启了软删除,还需要强行真实的永久删除,可以使用forceDelete()方法;
//开启软删除时的真实永久删除 $users = User::onlyTrashed()->find(82); $users->forceDelete();
20.模型的作用域
一.本地作用域
-
很多情况下,我们在数据查找时有一部分条件会被重复且大量使用;
-
而这个条件,可能只是在这个模型对应的数据表使用,别的表并不使用;
-
那么这种情况,可以使用本地作用域的方式,将常用的 SQL 封装起来;
-
比如:用户模块中,我们大量查询需要查询性别为男,且其它条件的SQL;
$users = User::where('gender', '男')->where('price', '>', 90)->get();
PS:我们可以将性别为男这个片段,封装成一个单独的方法,然后统一在这个模型下调用;
//App\Http\Models; //本地作用域,搜索自动添加为“男”的条件 //语法:scope 开头,后面名称尽可能包含语义 public function scopeGenderMale($query) {return $query->where('gender', '男'); } //当然,如果感觉单词太长,直接 gm()也行 $users = User::genderMale()->where('price', '>', 90)->get();
-
上面的方法比较死板,适合简单粗暴,如果想要灵活多变,支持传递参数;
//参数可以是 1 个或多个 $users = User::gender('女', -3)->where('price', '>', 90)->get(); //参数 2 和 3,接受控制器传递过来的 1,2 public function scopeGender($query, $value, $value2) {return $query->where('gender', $value)->where('status', $value2); }
二.全局作用域
-
全局作用域,顾名思义就是在任意地方都可以有效的封装条件;
-
比如有个需求,不管在哪里操作,总是显示 status 为 1 的用户;
-
首先在 app 目录下创建一个用于全局作用域的目录:Scopes;
-
创建一个用于设置 status 为 1 的全局作用域的类,它需要实现scope 接口;
namespace App\Scopes; //这里引用代码自动生成 use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Scope; class StatusScope implements Scope {/*** @inheritDoc*/public function apply(Builder $builder, Model $model){// TODO: Implement apply() method.return $builder->where('status', 1);} }
-
此时,还不能实现全局,因为需要在模型设置个开关,让其富有灵活性;
//启用全局作用域 protected static function booted() {parent::booted(); // TODO: Change the autogenerated stubstatic::addGlobalScope(new StatusScope()); }
PS:而在控制器端,并不需要做任何设置,即可自动添加 status=1 的条件;
-
当然,如果这个全局只是针对某个模块,并不需要创建一个全局类,直接闭包即可;
static::addGlobalScope('status', function (Builder $builder) {return $builder->where('status', 1); });
PS:注意 Builder 引入的文件和全局类引入的文件一致,如果引入别的同名类会错;
-
如果某个查询,并不需要这个全局条件,可以单独移出掉;
//取消名称为 status 的全局 $users = User::withoutGlobalScope('status')->get(); //取消全局类的条件 $users = User::withoutGlobalScope(StatusScope::class)->get();
PS:还有 withoutGlobalScopes([])方法,传递参数取消多个全局;
21.模型的访问器和修改器
一.访问器
-
访问器:就是在获取数据列表时,拦截属性并对属性进行修改的过程;
-
比如,我们在输出性别时,在性别左右加上括号,或给邮件转换为大写;
//访问器,前固定 get,后固定 Attribute,Gender 是字段名 //参数$value 是源字段值,可修改返回 public function getGenderAttribute($value) {return '【'.$value.'】'; }
PS:如果字段名是两个单词中间是下划线:
user_name
,那么方法名:getUserNameAttribute()
-
我们也可以创建一个虚拟字段,用已有的数据字段进行整合,不过要进行数据追加;
//将虚拟字段追加到数据对象列表里去 protected $appends = ['info']; //创建一个虚拟字段 public function getInfoAttribute() {return $this->username.'-'.$this->gender; }
PS:注意,如果 gender 之前已经有访问器修改过,上面的方法会得到修改过的结果;
PS:如果要使用源字段进行创建虚拟字段,需要使用下面这种:
return $this->attributes['username'].'-'.$this->attributes['gender'];
二.修改器
-
修改器,相对于访问器,是在写入的时候拦截,进行修改再写入;
//修改器,写入数据时,将邮箱转换为大写 public function setEmailAttribute($value) {$this->attributes['email'] = strtoupper($value); }
-
可以添加默认的日期列,默认 created_at 和 updated_at;
//设置可以自动写入日期的列 protected $dates = ['details' ];
-
可以设置字段输出的类型,比如设置一个布尔型,输出时就是true 和false;
//设置字段类型 protected $casts = ['details' => 'boolean' ];
22.集合的使用
一.创建集合
-
什么是集合?即:它是一种更具读取性和处理能力的数组封装;
-
比如,我们从数据库得到的数据列表,它就是一种集合;
-
数据集合,提供了大量的方法方便我们进行各种操作;
-
除了数据库对象返回的数据集合之外,我们还可以自行创建数据集合;
//创建一个数据集合 $collection = collect(['张三', '李四', '王五', null]); //使用 dd 查看它的类型 dd($collection); //直接 return 可以返回 return $collection;
-
数据集合提供了大概有三十多个(31?)处理数据集合的方法,可链式调用;
-
这里我们把最常用的演示一遍,剩下的所有,可以自行参考手册方法列表;
//以底层数组形式输出 return $collection->all(); //map 方法,类似访问器,可修改输出 return $collection->map(function ($value, $key) {return $key.'['.$value.']'; }); //支持链式,reject 移出非 true 的值 return $collection->reject(function ($value, $key) {return $value === null; })->map(function ($value, $key) {return $key.'['.$value.']'; }); //filter 筛选为 true 的值,和 reject 相反 return $collection->filter(function ($value, $key) {return $value === null; });//search 找到后返回 key,找不到返回 false return $collection->search('王五'); //集合的分割 return $collection->chunk(2); //迭代输出 $collection->each(function ($item, $key) {echo $item; });
PS:这里就介绍这么多,更多的我们去手册扫一遍。做项目时,凭着记忆回头查,慢慢就熟了;PS:下一节,我们会花一节课,把最常用的方法再运行一遍加深印象;
-
如果三十多个方法都没有你要的,还可以自定义方法,比如说所有英文大写;
$collection = collect(['Mr.Zhang', '李四', '王五', null]); Collection::macro('toUpper', function () {//dd($this);return $this->map(function ($value) {return strtoupper($value);}); }); return $collection->toUpper();
23.集合的常用方法
一.常用方法
-
all()方法,转换为属性形式输出,使用 dd 方法看类型;
$collection = collect([1, 2, 2, 3, 4, 4, 4]); dd($collection->all()); // PS:$collection->dd()方法可以以 dd()模式输出,还有 dump()模式;
-
avg()方法返回平均值;
//返回平均值 $collection = collect([1, 2, 3, 4]); return $collection->avg(); //返回分组平均值 $collection = collect([['男'=>1], ['女'=>1], ['男'=>3]]); return $collection->avg('男');
-
count()方法返回集合总数;
return $collection->count(); // PS:相关的还有 sum()、min()、max()等统计;
-
countBy()方法返回数值出现的次数或回调函数指定值出现的次数;
$collection = collect([1, 2, 2, 3, 4, 4, 4]); return $collection->countBy(); //回调搜索相同指定片段的值的次数 $collection = collect(['xiaoxin@163.com', 'yihu@163.com', 'xiaoying@qq.com']);return $collection->countBy(function ($value) {return substr(strrchr($value, '@'), 1); }); // PS:相关的还有 groupBy()、keyBy()方法;
-
diff()方法返回集合数组之间不相同的部分,组合新的集合;
//diff 返回两个集合中不相同的 $collection = collect([1, 2, 3, 4, 5]); return $collection->diff([3, 5]); // PS:其中还有 diffAssoc()、diffKeys()派生方法;
-
duplicates()返回重复的值;
$collection = collect([1, 2, 2, 3, 4, 5, 5, 6]); return $collection->duplicates(); //严格派生方法:duplicatesStrict()
-
first()返回成立后的第一个值;
//返回判断成立的第一条数值 $collection = collect([1, 2, 3, 4]); return $collection->first(function ($value) { return $value > 2; }); // PS:相关的还有 every()、except()、only()、firstWhere()、last()等方法;
-
flatten()将多维数组转换为一维;
$collection = collect(['name'=>'Mr.Lee', 'details'=>['gender'=>'男', 'age'=>100]]); return $collection->flatten();
-
get()通过键名找值;
$collection = collect(['name'=>'Mr.Lee', 'gender'=>'男']); return $collection->get('name'); // PS:相关的还有 pluck()等;
-
has()判断集合中是否存在指定键;
return $collection->has('name');
-
pop()移出集合中最后一个值;
$collection = collect([1, 2, 3, 4, 5]); //$collection->pop(); return $collection; // PS:相关的还有 pull()、push()、put()方法
-
slice()返回指定值后续的集合;
$collection = collect([1, 2, 3, 4, 5]); return $collection->slice(3);
PS:相关的还有 splice()等方法;
-
sort()返回指定值后续的集合;
$collection = collect([3, 1 , 5, 2, 7]); return $collection->sort()->values(); //需要配合 values()方法
PS:相关的有 sortBy()、sortByDesc()、sortKeys()等;
-
where()系列方法,和数据库条件一样;
$collection = collect([['name'=>'Mr.Lee', 'gender'=>'男'],['name'=>'Miss.Zhang', 'gender'=>'女'] ]); return $collection->where('name', 'Mr.Lee');
24.模型的数据集合
一.数据集合
-
数据集合,就是已经将模型方法 get()获取到的数据再进行处理;
-
比如:map()方法,通过它可以实现类似访问器一样对字段进行处理的效果;
$users = User::get(); //使用集合方法 map 可以对输出的字段进行过滤 $women = $users->map(function ($user) {$user->email = strtoupper($user->email);return $user; }); return [$women]
PS:数据集合支持连缀操作,和数据库连缀一样;
-
使用 reject()方法,可以获取条件之外的数据内容;
$women = $users->reject(function ($user) {return $user->gender != '女'; })->map(function ($user) {return $user; });
-
下面列出常用的集合方法列表:
//判断集合中是否包含指定的模型实例 return $users->contains(19); return $users->contains(User::find(19)); //返回不在集合中的所有模型 return $users->diff(User::whereIn('id', [19,20,21])->get()); //返回给定主键外的所有模型 return $users->except([19,20,21]); //集合也有 find 方法 return $users->find(19); //返回集合的数量 return $users->count(); //返回所有模型的主键 return $users->modelKeys(); //返回主键的所有模型 return $users->only([19,20,21]); //返回集合中的唯一模型 return $users->unique();
25.模型的一对一关联
一.关联概念
- 关联模型,即:两张或以上的表进行一定规则的绑定关联;
- 比如:一个学生(学生表)对应一张个人信息卡(信息表),这种就是一对一;
- 再比如:一篇博文(帖子表)对应多个评论(评论表),这种就是一对多;
- 再再比如:一个用户(用户表)对应多个职位(权限表), 而一个职位又可以有多个用户;那么,这种就是多对多关联;
- 自然,还有更多更复杂的关联,都是基于此的,我们只探讨这三种;
- 既然是关联,当然会有绑定的概念,当有数据库操作,关联表也会跟着变动;
- 这就是关联模型的意义;
二.一对一关联
-
一对一关联,我们选择两张表来做演示,首先看下两张表的结果对比;
-
由于之前 Models 下的 User.php 模型代码很多了,改成.bak 后重建;
-
创建两个 model,User.php 和 Profile.php,并使用命令实现提示;
php artisan ide-helper:models
//User.php,一对一关联 Profile 表 public function profile() {//参数 1 或:'App\Http\Models\Profile'//参数 2:默认为 user_id,如不是需要指明//参数 3:默认 id,如不是需要指明return $this->hasOne(Profile::class, 'user_id', 'id'); }
-
注意:Profile.php 只要建立空类即可,不需要其它,一对一调用如下:
//注意:->profile 不要加括号,以属性方式访问 $profiles = User::find(19)->profile; return $profiles;
-
一对一也支持反向关联:定向反向关联;具体通过在 Profile 设置即可;
//profile.php //参数 1 为主表类 //参数 2,3 和正向一致,默认对应可以不写 public function user() {return $this->belongsTo(User::class, 'user_id', 'id'); } $users = Profile::find(1)->user; return $users;
26.模型的一对多关联
27.模型的多对多关联
28.模型的关联查询
29.Debugbar调试器
30.模型的预加载
31.模型的关联写入
32.多对多的关联写入
33.请求和依赖注入
34.请求的常用方法
35.生成URL
36.Cookie
37.Session
38.理解中间件
39.中间件进阶
40.Blade模板入门
41.模版的流程控制
42.模板的继承布局
43.表单快速验证
44.验证类的使用
45.创建手动验证
46.验证规则大全
47.数据分页
相关文章:
Laravel基础
Laravel 基础 01.Laravel入门和安装 Composer安装Laravel步骤 要使用 Composer 安装 Laravel,请按照以下步骤操作: 确保已经安装了 Composer。如果还没有安装,请访问 https://getcomposer.org/download/ 下载并安装。 打开命令行或终端。…...
【Leetcode 每日一题 - 补卡】2302. 统计得分小于 K 的子数组数目
问题背景 一个数组的 分数 定义为数组之和 乘以 数组的长度。 比方说, [ 1 , 2 , 3 , 4 , 5 ] [1, 2, 3, 4, 5] [1,2,3,4,5] 的分数为 ( 1 2 3 4 5 ) 5 75 (1 2 3 4 5) \times 5 75 (12345)575。 给你一个正整数数组 n u m s nums nums 和一个整数 k…...
力扣——206.反转链表倒序输出链表
206. 反转链表 - 力扣(LeetCode) 思路(迭代) 设三个指针,前后两个指针都为空,当前指针为输入的头指针 开始循环——判断条件为当前节点不为空 先给下一个节点赋值为——当前节点的下一个 改变当前节点的…...
Arthas在Java程序监控和分析中的应用
Arthas在Java程序监控和分析中的应用 在互联网大厂Java求职者的面试中,经常会被问到关于使用Arthas来监控和分析Java程序的相关问题。本文通过一个故事场景来展示这些问题的实际解决方案。 第一轮提问 面试官:马架构,欢迎来到我们公司的面…...
第13讲:图形尺寸与分辨率设置——适配论文版面,打造专业图稿!
目录 📌 为什么这一讲重要? 🎯 一、先认识几个关键词 ✍️ 二、ggsave() 是导出图的标准方法 📐 三、尺寸设置技巧:对齐目标期刊 🔍 找到目标期刊的图形栏宽 📦 四、多个图组合导出(与 patchwork 搭配) 🧪 五、使用 Cairo / ragg 导出高质量图 🎁 六…...
Docker与Vmware网络模式的对别
前言 在使用了很久的VMware和Docker后,分别独立配置过他们的网络,但是每次配置一方时,总感觉和另一方有点不一样,但是也没有来得及总结。刚好最近有时间可以总结一下。 重点: 1、VMware的桥接模式和Docker的桥接模式完…...
大模型在肾癌诊疗全流程中的应用研究报告
目录 一、引言 1.1 研究背景与意义 1.2 研究目的与方法 1.3 国内外研究现状 二、大模型预测肾癌术前情况 2.1 基于影像组学的肾癌良恶性及分级预测 2.1.1 MRI 影像组学模型预测肾透明细胞癌分级 2.1.2 CT 影像深度学习模型鉴别肾肿物良恶性及侵袭性 2.2 大模型对手术风…...
Springboot使用登录拦截器LoginInteceptor来做登录认证
创建拦截器LoginInteceptor类 interceptors/LoginInteceptor.java package org.example.interceptors;import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.example.utils.JwtUtil; import org.springframework.s…...
2025年- H13-Lc120-189.轮转数组(普通数组)---java版
1.题目描述 2.思路 import java.util.Arrays;public class H189 {public static void main(String[] args) {int[] newArr {1, 2, 3, 4, 5};int[] nums new int[5];System.arraycopy(newArr,0,nums,0,4);System.out.println(Arrays.toString(nums)); } }补充2: 3.…...
Android Framework常见问题
以下是不同难度级别的 Android Framework 面试题,包含答案要点,可帮助你为面试做好准备。 初级难度 1. 请简要解释 Android Framework 是什么。 答案要点:Android Framework 是 Android 系统的核心组成部分,它为开发者提供了一…...
【AI】图片处理的AI工具
博主最近需要给客户展示一下做的一些设备和仪器,随手拍了一些照片,觉的背景不是很好看,于是在网上寻找AI图片处理工具。后来随手用了一下豆包AI,发现很好用,这里把一点使用的心得体会记录一下,并和大家分享…...
Python列表全面解析:从基础到高阶操作
一、为什么需要列表? 在Python中,列表是可变有序序列,用于存储多个元素的容器。相较于单一变量存储独立值,列表能更高效地管理批量数据,其特点包括: 引用存储:列表元素存储的是对象的引用…...
C++调用C动态库编译时报undefined reference to “funcxxx“错误
问题描述:Linux平台上C调用C库进行make编译时报undefined reference to "funcxxx"错误,错误实例如下: /usr/bin/ld: CMakeFiles/dialog.dir/widgets/widget.cpp.o: in function Widget::loadVerificationModule(): /home/zhangxia…...
基于Spring Boot+Vue 网上书城管理系统设计与实现(源码+文档+部署讲解)
技术范围:SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论文…...
C++ 中自主内存管理 new/delete 与 malloc/free 完全详解
C 中 new/delete 与 malloc/free 完全详解 一、new/delete 与 malloc/free 的区别 特性new/deletemalloc/free属于C语言C语言申请的内存区堆(Heap)堆(Heap)返回类型指向对象类型的指针(自动转换)void*&…...
Maven中的依赖管理
目录 什么是依赖范围 什么是依赖传递 依赖范围对依赖传递的影响 依赖冲突 什么是依赖冲突 依赖冲突的解决方案 版本锁定 短路径优先 编辑 声明优先 特殊优先(后来者居上) 可选依赖 排除依赖 可选依赖和排除依赖的区别 刷新依赖的8种方式…...
生态修复项目管理软件
在“双碳”目标与生态文明建设的双重驱动下,生态修复项目正成为全球环境治理的核心战场。然而,矿山复绿、湿地修复、水土保持等工程往往面临跨地域、多主体、长周期的管理难题——从数据分散到进度失控,从成本超支到风险频发,传统…...
深度剖析 RocketMQ 5.0 之架构解析:云原生架构如何支撑多元化场景?
拓展学习:🔍「RocketMQ 中文社区」 持续更新,提供 RocketMQ 领域专家模型的 AI 答疑 作者 | 隆基 简介: 了解 RocketMQ 5.0 的核心概念和架构概览;然后我们会从集群角度出发,从宏观视角学习 RocketMQ 的管…...
Spring中bean的生命周期(笔记)
bean的生命周期,按照最重要五步 第一步:实例化bean,调用无参构造方法(通过BeanDefinition利用反射实例化Bean对象(无参数构造方法) 并通过推断构造方法...并放入三级缓存中..) 第二步:给bean属性赋值(调用…...
transform-实现Encoder 编码器模块
Encoder 论文地址 https://arxiv.org/pdf/1706.03762 Encoder结构介绍 Transformer Encoder是Transformer模型的核心组件,负责对输入序列进行特征提取和语义编码。通过堆叠多层结构相同的编码层(Encoder Layer),每层包含自注意力机…...
LVGL -窗口操作
1 窗口背景介绍 在 LVGL 中,screen 是一个顶层对象,代表你设备上当前显示的整个画面。它相当于一个“全屏容器”,你可以在上面添加按钮、标签、图像、容器等各种界面控件。它的本质就是一个特殊的 lv_obj_t,但它没有父对象&#…...
ollama运行qwen3
环境 windows server GPU 32G 内存 40G 升级ollama 需要版本 0.6.6以上 ollama --version拉取模型 ollama pull qwen3:32b时间比较长,耐心等待 运行模型 ollama run qwen3:32b运行起来之后发现GPU是可以跑起来的,发个你好看看 默认是深度思考的,不…...
如何查看和验证AWS CloudFront的托管区域ID
在使用AWS Route 53设置DNS记录时,正确识别CloudFront分发的托管区域ID是至关重要的。本文将详细介绍几种查看和验证CloudFront托管区域ID的方法,特别关注中国区CloudFront的特殊情况。 为什么托管区域ID很重要? 托管区域ID是AWS服务中的一个关键标识符。在创建指向CloudF…...
yum 安装 ncurses-devel 报错 baseurl 的解决方法
解决 yum 安装 ncurses-devel 报错(baseurl 问题) 出现 yum install ncurses-devel 报错 Cannot find a valid baseurl for repo: centos-sclo-rh/x86_64 的原因,很可能是因为 CentOS 7 的 SCL 源在 2024 年 6 月 30 日停止维护了。以下是解…...
《Vue3学习手记7》
组件通信(续) $attrs 组件通信:$attrs 适用于祖传孙或孙传祖 (需要通过中间组件) 传递给后代的数据,但未被接收,都保存在attrs中 1.祖传孙 父组件: <template><div cl…...
算法备案类型解析:如何判断你的算法属于哪种类型?
根据《互联网信息服务算法推荐管理规定》政策,算法备案已经成为了强制性备案。但对于企业而言,如何准确判断自身算法所属的备案类型往往存在困惑,今天我们就来详细盘一盘算法备案的类型,教你如何判断自己的算法属于哪一类 一、算…...
Javascript 中作用域的理解?
一、作用域的类型 1. 全局作用域(公司大门外) 范围:整个 JavaScript 文件变量:像贴在公告栏上的信息,所有人可见例子:const companyName "阿里"; // 全局变量,任何地方都能访问 fu…...
Qt入门——什么是Qt?
Qt背景介绍 什么是Qt? Qt 是⼀个 跨平台的 C 图形用户界面应用程序框架 。它为应用程序开发者提供了建立艺术级图形界面所需的所有功能。它是 完全面向对象 的,很容易扩展。Qt 为开发者提供了 ⼀种基于组件的开发模式 ,开发者可以通过简单的拖拽和组合…...
Snap7西门子PLC通信协议
S7协议,作为西门子的专有协议,广泛应用于多种通讯服务中,如PG通讯、OP通讯以及S7基本通讯等。它独立于西门子的各种通讯总线,能够在MP、PROFIBUS、Ethernet以及PROFINET等多种网络上运行。S7协议实质上是一个由多种应用层协议构成…...
GTC Taipei 2025 医疗域前瞻:从AI代理到医疗生态,解码医疗健康与生命科学的未来图景
引言 2025年,全球医疗健康领域正经历一场由人工智能、机器人技术与分布式计算驱动的范式转移。随着NVIDIA及其生态伙伴在GTC Taipei 2025大会上的深度布局,医疗行业的核心趋势愈发清晰:AI代理程序(Digital AI Agents)赋能临床协作、医疗大数据与精准医学加速落地、医学影…...
C++的vector中emplace_back() 与 push_back() 的区别
C 中 vector 的 emplace_back() 和 push_back() 均用于向容器末尾添加元素,但二者在实现和效率上有显著区别: 1. 参数传递方式 push_back():接受一个已构造的对象(左值或右值),将其拷贝或移动到容器中。 s…...
LangChain4j +DeepSeek大模型应用开发——5 持久化聊天记忆 Persistence
默认情况下,聊天记忆存储在内存中。如果需要持久化存储,可以实现一个自定义的聊天记忆存储类,以便将聊天消息存储在你选择的任何持久化存储介质中。 1. 存储介质的选择 大模型中聊天记忆的存储选择哪种数据库,需要综合考虑数据特…...
C++核心编程 1.2 程序运行后
1.2 程序运行后 栈区: 由编译器自动分配释放, 存放函数的参数值,局部变量等 注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放 int * func() {int a 10;return &a; }int main() {int *p func();cout << *p <…...
小市值策略复现(A股选股框架回测系统)
相关config配置 https://quantkt.com/forumDetail?id201043 很早就知道了小市值模型,正好量化选股回测框架出来了,把最裸的小市值复现下,顺便验证下框架逻辑。 科普: 小市值策略基于 “小市值效应”,即从历史数据来看…...
C语言(6)—函数递归
文章目录 一、递归的基本概念1.1 什么是递归1.2 递归的核心思想1.3 递归的必要条件 二、递归的经典应用2.1 阶乘计算 三、递归与迭代的比较3.1 递归的优缺点3.2 迭代的优缺点 四、递归的底层机制4.1 函数调用栈4.2 栈溢出风险 五、递归优化技巧5.1 记忆化(Memoizati…...
【网络】HTTP报文首部字段
目录 一. 预备知识 1.1.代理、网关和隧道 1.1.1.代理 1.1.2.网关 1.1.3.隧道 1.2.保存资源的缓存 1.2.1.缓存的有效期限 1.2.2.客户端的缓存 1.3.用单台虚拟主机实现多个域名 二. HTTP首部字段 2.1.HTTP 首部字段格式 2.2.四种 HTTP 首部字段类型 三. HTTP通用首部…...
【Fifty Project - D20】
今日完成记录 TimePlan完成情况7:30 - 11:30收拾行李闪现广州 & 《挪威的森林》√10:00 - 11:00Leetcode√16:00 - 17:00健身√ Leetcode 每日一题 每日一题来到了滑动窗口系列,今天是越…...
【Linux系统】systemV共享内存
system V共享内存 在Linux系统中,共享内存是一种高效的进程间通信(IPC)机制,它允许两个或者多个进程共享同一块物理内存区域,这些进程可以将这块区域映射到自己的虚拟地址空间中。 共享内存区是最快的IPC形式。一旦这…...
【计算机网络】DHCP——动态配置ip地址
DHCP 是什么? DHCP(Dynamic Host Configuration Protocol,动态主机配置协议) 是一种网络协议,用于自动分配 IP 地址和其他网络配置参数(如子网掩码、默认网关、DNS 服务器等)给网络中的设备&…...
TDengine 订阅不到数据问题排查
简介 TDengine 在实际生产应用中,经常会遇到订阅程序订阅不到数据的问题,总结大部分都为使用不当或状态不正确等问题,需手工解决。 查看服务端状态 通过 sql 命令查看有问题的 topic 和consumer_group 组订阅是否正常。 select * from inf…...
低版的spring boot 1.X接入knife4j
低版的spring boot 1.X接入knife4j 老的项目是用spring boot 1.5.10.RELEASE 不好升级 ,原来接口文档一直用的是老的swagger样式,不是很好看,网上查了下,发现有个knife4j挺好看的,用一下他们的样式,下面是…...
outlook for mac本地邮件存放在哪儿?
尽管 PST 格式通常与 Microsoft Outlook 联系在一起,但认为它也在 Mac OS 上存储邮箱数据是一种误解。实际上,Outlook for Mac 不会将邮件存储为 PST 文件。无法在 Outlook for Mac 中找到 PST 文件位置,因为它不使用 PST 文件来存储邮箱数据…...
javascript<——>进阶
一、作用域:变量可以被访问的范围 1.局部作用域 1.1函数作用域 在函数内部声明的变量,在函数内部被访问的,外部无法直接访问。 总结:1、函数内部声明的变量,在函数外部无法直接访问 2、函数的参数也是函数内部的局…...
Java练习8
一.题目 二.源码 package TestRuMen;import java.util.Random; import java.util.Scanner;public class Test11 {public static void main(String[] args){// 调用 createNumber 方法生成一组随机的中奖号码int[] arrcreateNumber();// 调用 userInputNumber 方法获取用户输入…...
C语言按位操作符
在C语言中,按位操作符直接对整数的二进制位(bit)进行操作,常用于底层编程、硬件控制或性能优化场景。以下是按位操作符的详细说明和用法: 1. 按位操作符列表 操作符名称功能描述示例&按位与对应位均为1时结果为1&…...
Linux(权限管理)
权限概述 基本概念 定义:Linux权限是操作系统对用户和进程访问资源进行精细化管控,通过读(r4)、写(w2)、执行(x1)三种基础权限组合实现。 其中在运维的角度看它们所对应的操作权限…...
Redis入门到实战——基础篇
一、初识Redis 1. 认识NoSQL 2. 认识Redis Redis诞生于2009年,全称Remote Dictionary Server,远程词典服务器,是一个基于内存的键值型NoSQL数据库。 特征: 键值型(key-value),value支持多种…...
ctf.show 卷王杯 pwn签到
pwn签到 64位 ret2libc pwn签到 (1) motalymotaly-VMware-Virtual-Platform:~/桌面$ file pwn pwn: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]0953abcf1dd6…...
Dali 1.1.4 | 使用尖端技术将描述转换成独特艺术品、照片和图像,发挥无限创意
Dali是一款先进的AI图像生成器应用程序,能够根据用户的描述生成不同风格的独特图像。无论是迷人的数字艺术、创新的纹身设计还是独一无二的标志,甚至是超写实的照片,Dali都能轻松应对。这款解锁版为用户提供了更多的创作自由度和无限可能&…...
LeetCode 2906 统计最大元素出现至少K次的子数组(滑动窗口)
给出一个示例: 输入:nums [1,3,2,3,3], k 2 输出:6 解释:包含元素 3 至少 2 次的子数组为:[1,3,2,3]、[1,3,2,3,3]、[3,2,3]、[3,2,3,3]、[2,3,3] 和 [3,3] 。该题也是一个比较简单的滑动窗口的题目,但是…...