Repository navigation
Laravel基于项目实践的源码剖析 #2
Description
Activity
- pinned this issue
on May 10, 2022 目录结构与配置
- 目录结构
Laravel的目录与文件很多,常用的有如下这些:
app: 当前应用相关的内容,如控制器, 中间件, 服务提供者等config: 当前应用的所有配置文件public: 外部可访问的Web目录,包括入口文件与前端静态资源routes: 所有的路由定义文件resources: 目录包含了视图和未编译的资源文件(如 LESS、SASS 或 JavaScript)vendor: 包含安装的所有的 Composer 依赖包.env: 环境配置信息,如应用, 数据库,会话, 邮箱,Redis等artisan: Laravel 内置的命令行工具composer.json,composer.lock: Composer依赖包的声明与锁定文件
请注意需要将Public作为网站的入口
- 环境配置
- 如果是使用
Laragon|Composer安装的,.env文件会自动创建和配置好。否则需要手工更名.env.example为.env - .env中的内容可通过辅助函数
env()调用:主要在config/目录下面的配置文件中进行调用, 如config/app.php
- 数据库
在
.env中可以设置数据库连接的相关参数:DB_CONNECTION=mysql DB_HOST=localhost DB_PORT=3306 DB_DATABASE=blog DB_USERNAME=root DB_PASSWORD=root可以使用
Adminer来连接数据库,并创建相关的数据表和导入数据等。- 目录结构
MVC流程分析
Laravel是基于MVC架构的开源PHP框架
- M:Model,数据库操作,也包括队列缓存等
- V:View,视图,由一个或多个模板组成,格式化输出响应数据
- C:Controller,控制器,主要接收请求,选择模型,渲染视图等
1. Web应用程序运行原理:URL请求 -> 路由解析 -> 执行程序
Laravel中的所有Web访问,必须设置路由才会生效
对php来说,一个页面,对应一个php脚本文件,以dev.tt/index.php为例:
- Laravel将URL请求的地址,映射到index.php上的功能,成为路由技术
- Laravel中路由文件是:
routes/web.php dev.tt/index.php:一个页面, 对应的就是一个应用程序- 程序就是可执行的代码, 代码为了复用通常使用函数进行封装,然后按名调用
- 在Laravel中, 函数有二个载体: 闭包(匿名函数), 类中的方法
- 浏览器地址栏输入:
dev.tt/index.php, 就可以看到输出的HTML内容
2. 控制器
- 接受请求: 请求来自客户端, 客户端不限于浏览器, 可以是命令行, App(如小程序)等
- 选择模型: 当涉及数据处理时, 需要调用对应的模型与不同的数据库进行交互
- 渲染视图: 大多数操作都需要返回一个HTMl页面给用户,所以需要模板调度与渲染
- Laravel中控制器默认应该创建在
app/Http/Controllers目录下面 - 控制器类的命名空间(
App\Http\Controllers)与它的访问路径是一致的, 便于自动加载 - 所有控制器必须继承控制器基类
app\Http\Controllers.php, - 该基类与用户控制器类在同一目录,继承时不必使用带有命名空间名称的基类名
- 创建控制器有二种方法: **手工创建,命令创建
3. 模型
- 从工程角度看, 模型是对现实世界的抽象, 通过研究模型可以推导出真实世界的情况
- 计算机世界的模型,特指数据模型, 是对物理数据, 按照人类思维方式的抽象或简化
- 传统的Web项目, 数据几乎全部来自数据库, 所以模型大多是与数据库相关的操作
- 随着Web应用程序的日趋复杂,缓存,队列等不断的加入,使模型的定位也越来越模糊
- 所以, Laravel中并未对模型设置默认或专用的目录, 一切交给用户决定
- 做为新人, 应该从模型的最基本应用场景开始学习: **操作数据表
- 虽然框架没有为模型设置默认位置, 但是模型总是与应用相关, 所以通常创建在
app目录下面 - 在
app目录下, 可以直接创建一个模型类, 不过, 更推荐创建一个模型目录app/models统一管理 - Laravel 的 Eloquent ORM 提供了一个漂亮, 简洁的 ActiveRecord 实现来和数据库交互
- 每个数据库表都有一个对应的 [模型] 用来与该表交互
- 可以通过模型查询数据表中的数据, 以及在数据表中插入新记录
- 与控制器一样, 模型也有: 手工创建, 命令创建二种方式
4. 视图
- Laravel 使用的是 Blade模板引擎, 使用它创建的模板文件中, 可以直接使用原生的PHP代码
- Blade模板引擎生成的模板文件:
- 默认存放目录:
resources/views - 默认扩展名是:
.blade.php
- 默认存放目录:
- 通常模板文件与控制器方法对应(但也并不总是如此)
- Laravel没有给提供创建视图的命令, 但你可以通过安装composer扩展
sven/artisan实现
中间件
- 中间件,并非Laravel首创, 也不是什么新概念
- 中间件一直都存在, 只不过之前没有得到充分的重视
- 中间件,顾名思义, 应该位于二个事物的中间,是哪二个事物呢?
- 简单理解, 中间件位于用户请求与控制器之间, 其实还有基本的应用场景
- 例如, 中间件可以过滤用户的请求, 或者拦截进入到控制器的请求数据
- 所以, 中间件是请求的过滤器, 控制器的拦截器(保镖)
1. 中间件执行流程
中间件也是一个类文件, 主要有创建, 注册, 触发三个阶段
- 创建阶段: 可以手工创建, 但更推荐使用
artisan命令方式创建 - 注册阶段: 注册工作在
app/Http/kernel.php内核文件中完成 - 触发阶段: 可以在路由中定义
routes/web.php,或者在控制器的构造方法中触发
2. 中间件创建
- 中间件默认创建在:
app/Http/Middleware目录下面, 当前已经内置了一些常用的中间件 - 手工创建中间件, 推荐复制一个现成的, 然后将业务逻辑更新成自己的
- 中间件不需要继承中间件的基类,也可以正常工作
- 中间件中只有一个必须实现的方法
handle() - 命令创建:
php artisan make:middleware CheckNameMiddleware
# app/Http/Middleware/CheckNameMiddleware.php # 功能: 检测当前URL访问的用户名,判断是否有权访问 # 如果用户名name='admin', 则是管理员, 必须登录(跳转到登录页面),以获取更多的权限 # 其它用户, 则直接进入页面,直接显示一行欢迎词即可 namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; class CheckNameMiddleware { public function handle(Request $request, Closure $next) { // 如果请求中的name参数值不是admin, 则重定向到登录页面, 这里只是模拟 if ($request->name === 'admin') { return redirect('login'); } return $next($request); } }3. 中间件的注册
中间件(以全局为例)的注册完全在
app/Http/kernel.php中, 需要分二步完成:- 将中间件注册/添加到
protected $middleware=[...]数组中 - 为中间件创建路由别名/键名:
protected $routeMiddleware = [...]数组
protected $middleware = [ ... // 注册自定义的中间件 \App\Http\Middleware\CheckNameMiddleware::class, ]; protected $routeMiddleware = [ ... // 注册自定义中间件的路由 'checkName' => \App\Http\Middleware\CheckNameMiddleware::class, ];4. 中间件的触发
中间件涉及到请求, 控制器,视图等, 为了简化, 我们只在路由中进行测试:
routes/web.php// 测试中间件, 为简化直接用闭包实现, 不做视图了 // 这是中间件验证失败后的跳转路由 Route::get('login', function () { return '<h1>登录页面</h1>'; }); // 中间件验证路由 Route::get('home/{name}', function ($name) { return '<h3>欢迎用户'.$name. '回来</h3>'; })->middleware('checkName');
路由
- 路由的最基本功能是将一个URL请求, 映射到一个程序程序(Web页面)
- Laravel路由主要是通过
Route这个Facade类来声明的 - 路由主要是由路由动作和路由行为组成, 如:
Route::get('hello', funciton (){...}) - 路由参数主要用在闭包路由中
- Laravel中的路由主要由二个文件定义:
web.php: 主要处理浏览器发起的URL请求, 这是我们学习的主要对象api.php: 主要用于API接口请求,通常用在跨语言, 跨应用的环境中
1. 路由动作
Laravel中的路由地址, 遵循
RESTful风格, 所以,具体的路由动作/类型,保持与RESTful一致动作/方法 SQL语句 实例 描述 GET SELECT GET: http://dev.tt/staffs 获取资源 POST CREATE POST: http://dev.tt/staffs 创建资源 PUT UPDATE PUT: http://dev.tt/staffs/5 更新资源 PATCH UPDATE PATCH: http://dev.tt/staffs/7 更新资源部分属性 DELETE DELETE DELETE: http://dev.tt/staffs/10 删除资源 常用的路由动作:
Route::get('url', function () {...}); Route::post('url', function () {...}); Route::put('url', function () {...}); Route::patch('url', function () {...}); Route::delete('url', function () {...});
如果一个请求,需要响应多个动作,可以这样做:
// 可响应GET/POST请求 Route::match(['get', 'post'], 'url', function () {...}); // 可响应任何请求 Route::any('url', function () {...});
2. 路由参数
- 路由是将URL请求映射到一个函数的技术,而函数通常都会有一个或多个参数
- 参数通过URL传入到函数中, 是非常方便,且自然的想法
- Laravel路由支持通过路由传参, 参数格式遵循
RESTful规范 - 参数根据是否必须赋值,可分为: 必选参数和可选参数二类
2.1 必选参数
- 所谓必选, 就是在路由中必须提供, 如果不提供,或类型错误都会报错
- 必须使用一对花括号
{...}包裹,多个参数之间使用/分隔,如add/{x}/{y}
在
routes/web.php中, 定义一个闭包路由进行演示Route::get('sum/{a}/{b}', function ($a, $b) { return "$a + $b = " . ($a + $b); });
- 注意: 函数中的参数与URL中的参数, 只和顺序相关, 与名称无关, 显然名称一致可读性更好
2.2 可选参数
- 所谓可选, 就是在路由可以提供参数,如果不传参, 则使用默认值
- 所以, 可选参数,在函数的参数列表中,必须提供默认值
- 语法非常简单, 只需要在路由参数后面添加一个
?号即可,如url/id?
Route::get('sum/{a?}/{b?}', function ($x = 15, $y = 25) { return "$x + $y = " . ($x + $y); }); // 浏览器: dev.tt/sum, 未传参则使用默认值, 返回 15 + 25 = 40 // 浏览器: dev.tt/sum/30/35,传参则使用自定义的值 返回: 30 + 35 = 65
2.3 参数类型约束(了解即可)
- 可以对参数类型进行约束, 例如上例中的,二数相加, 二个操作数必须是数值才有意义
- Laravel路由提供了
where()方法,对参数类型进行约束, 可以使用正则做为条件 - 放心, 只需要几个非常简单的正则即可,如
\d+,\w+
Route::get('sum/{a}/{b}', function ($a, $b) { return "$a + $b = " . ($a + $b); })->where('a', '\d+')->where('b', '\d+');
- where()支持链接调用, 参数除字符串,还可以是数组,将多个条件写在数组中
Route::get('sum/{a}/{b}', function ($a, $b) { return "$a + $b = " . ($a + $b); })->where(['a'=>'\d+', 'b'=>'\d+']);
- 除了闭包路由外, 还可将参数传递给指定的控制器方法, 其实这样做更常用
# 控制器方法: TestController.php public function sum($a, $b) { return "$a + $b = " . ($a + $b); } # 路由定义 Route::get('sum/{a}/{b}', 'TestController@sum');
3. 命名路由
- 命名路由, 也叫别名路由, 就是使用一个语义化的字符串来代替(简化)一个较长的路由名称
- 使用
name()方法创建命名路由, 在需要访问路由的地方,使用route()进行解析并还原
// 为当前路由'sum/{a}/{b}',创建一个命名路由: 'add' Route::get('sum/{a}/{b}', function ($a, $b) { return "$a + $b = " . ($a + $b); })->name('add');
- 命名路由创建成功后, 就可以在视图,控制器或者其它路由中使用了:
// 为了简化, 在另一个闭包路由中调用这个命名路由 Route::get('res', function (){ $url =route('add',[100,200]); return '<a href="'.$url.'">计算</a>'; });
- 还有一个辅助方法:
url(), 功能与route()类似,但不支持命名路由, 修改一下res
Route::get('res', function (){ $url =url('sum',[100,200]); return '<a href="'.$url.'">计算</a>'; }); // 执行结果与上面是完全一样的
4. 闭包路由
- 其实前面我们已经在用闭包路由了, 大家应该不会感到陌生
- 闭包, 其实就是一个匿名函数,如果应用比较简单, 不需要控制器和视图介入, 使用闭包最方便
- 注意, 闭包中的返回数据, 不要使用
echo,应该统一使用return, 由响应对象决定如何返回前端
Route::get('dev', function () { return 'Hello dev.tt';});
- 注意, 第二个参数必须是一个闭包, 不能是一个函数名称,例如这样做是错误的
Route::get('dev', 'hello'); function hello { return 'Hello dev.tt'; }
5. 控制器路由
- 这可能是大家用得最多的一种路由方式了, 其实所有的路由都可以用它代替的
- 控制器默认创建在
app\Http\Controllers目录下面 - 控制器名称与方法之间用
@符区隔开, 如DemoController@index
namespace App\Http\Controllers; class DemoController extends Controller { public function index() { //... } }
- 定义路由:
Route::any('demo', 'DemoController@index');
控制器
1. 控制器与MVC
1.1 控制器简介
- 随着项目的复杂度提升, 不可能将所有业务逻辑全部写到路由的闭包中
- 最常用也是最有效的方式,就是创建控制器, 将业务逻辑写到控制器的方法中
- 前面学过, 控制器是MVC架构中的重要组成部分
1.2 控制器与MVC
- MVC: M模型(Model), V视图(View), C控制器(Controller)
- 大多数项目是基于CURD操作来实现的, 那么他么的MVC流程是这样的:
-
从模型类获取数据,通过控制器,渲染到视图页面中
-
从视图页面中获取用户提交的请求/数据:
- 涉及数据库,则交给模型类处理,并将结果返回或存储到数据库中
- 不涉及模型类, 则直接在控制器中处理,并返回给用户
-
控制器不应该承载太多的功能,他更适合充当路由请求的对象
-
控制器并非应用访问的唯一入口,例如Artisan, 队列,任务调度等
-
过度依赖控制器会带来以后应用扩展的麻烦,所以,控制器的功能应该专业,单一
1.3 控制器的功能
- 获取HTTP请求, 并进行简单的验证
- 然后将这些请求交给真正能处理这些请求的职能模块完成
- 类似于公司的前台或企业的门卫, 识别请求, 验证身份, 通知职能部门处理
2. 控制器的创建
2.1 控制器命名空间与基类
-
命名空间:
app/Http/Controllers -
继承基类:
app/Http/Contrllers/Contrller.php -
创建命令:
php artisan make:controller StaffController -
准备工作,将之前创建的控制器, 模型和无关视图删除, 全部重新创建
// 默认控制器代码 namespace App\Http\Controllers; use Illuminate\Http\Request; class StaffController extends Controller { // }
- 创建一个默认方法, 获取所有员工信息
namespace App\Http\Controllers; use App\Models\StaffModel; use Illuminate\Http\Request; class StaffController extends Controller { //获取员工列表 public function index() { // view(): 选择视图模板文件,注意这里使用目录结构语法"." // StaffModel::all(): 模型中的all()方法返回全部表中全部记录 return view('staffs.index') ->with('staffs', StaffModel::all()); } }
- 模型使用之前创建的, 代码如下:
namespace App\Models; use Illuminate\Database\Eloquent\Model; class StaffsModel extends Model { // 模型名单数, 默认对应复数名的数据表 protected $table = 'staffs'; // 默认对应的主键字段名称是id protected $primaryKey = 'id'; // 因为create_at,update_at使用了laravel默认名称, 所以可以直接这样写 // 这样可以保证,以时间戳的形式存入时间 protected $dateFormat = 'U'; }
- 视图:
resource/views/staffs, 视图支持.php和.blade.php二种语法 - 以下是原生PHP语法
// 原生PHP语法 <!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>员工管理</title> </head> <body> <h3>员工列表</h3> <ul> <?php foreach ($staffs as $staff): ?> <li><?=$staff['id']?>: <?=$staff['name']?> email: <?=$staff['email']?></li> <?php endforeach;?> </ul> </body> </html>
- 以下是
Blade模板语法
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>员工管理</title> </head> <body> <h3>员工列表</h3> <ul> @foreach ($staffs as $staff) {{-- 为了以示区别, 将email邮箱字段修改为salary工资字段 --}} <li>{{$staff['id']}} : {{$staff['name']}} salary: {{$staff['salary']}}</li> @endforeach </ul> </body> </html>-
存在以上二种格式的模板时,
.blade.php的模板文件优先读取 -
创建路由, 访问该方法
Route::get('staffs', 'StaffController@index');
2.2 获取用户的输入
以添加一个员工数据为例
- 添加操作需要二步,首先渲染一个表单, 然后再将提交的数据写到表中
- 先添加二条路由:
routs/web.php
// create(): 渲染表单 // Route::get('staffs/create', 'staffsController@create'); // 如果只是渲染一个模板, 没有必要创建一个控制器方法的,可以直接用闭包简化 //Route::get('staffs/create', function () { // return view('staffs.create'); //}); // 还可以使用路由中的view()方法进一步简化 Route::view('staffs/create', 'staffs.create'); // store(): 存储数据 Route::post('staffs/store', 'StaffController@store');
- 模板文件:
resources/views/staffs/create
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>添加员工</title> </head> <body> <h3>添加员工</h3> {{-- url('路由'): 将路由解析为对应的真实请求地址 --}} <form action="{{url('staffs/store')}}" method="post"> {{-- 表单令牌,会自动转为隐藏域发送,没有它请求会被服务器拒绝 --}} @csrf <p> <label for="name">姓名:</label> <input type="text" name="name" id="name"> </p> <p> <label for="male">性别:</label> <input type="radio" name="sex" id="male" value="1"><label for="male">男</label> <input type="radio" name="sex" id="female" value="0"><label for="female">女</label> </p> <p> <label for="age">年龄:</label> <input type="number" name="age" id="age" min="18" max="60"> </p> <p> <label for="salary">工资:</label> <input type="number" name="salary" id="salary" min="2500"> </p> <p> <label for="email">邮箱:</label> <input type="email" name="email" id="email"> </p> <p> <label for="mobile">手机:</label> <input type="tel" name="mobile" id="mobile"> </p> <p> <label for="password">密码:</label> <input type="password" name="password" id="password" value="123456"> </p> <p> <button>保存</button> </p> </form> </body> </html>StaffController: 获取用户请求的数据,并处理
namespace App\Http\Controllers; use App\Models\StaffModel; use Illuminate\Http\Request; class StaffController extends Controller { //获取员工列表 public function index() { return view('staffs.index')->with('staffs', StaffModel::all()); } // 渲染添加表单 public function create() { return view('staffs.create'); } // 添加数据操作 // 依赖注入: 请求对象, 模型对象 public function store(Request $request, StaffModel $staff) { // 获取表单提交的数据,这里直接使用魔术属性 $staff->name = $request->name; $staff->sex = $request->sex; $staff->age = $request->age; $staff->salary = $request->salary; $staff->email = $request->email; $staff->mobile = $request->mobile; $staff->password = sha1($request->password); // 将获取到的全部表单数据存储到模型对应的数据表中 $staff->save(); // 保存成功后, 跳转到所有员工列表页 return redirect('staff'); } }
2.3 再谈依赖注入
2.3.1 门面与依赖注入之间的关系
- 门面(Facade)是Laravel为大部分类提供的简单调用接口, 可轻松获取各种请求数据
- 例如用户的输入,
Session,Cookie等,但并不是所有类都是门面接口 - 其实对于这些类, 我们还可以可通过更底层的依赖注入来调用它们
- 本质上说过, 门面仅仅是一种设计模式,是对底层API做的上层静态代理,主要是为了简化调用
- 所以, 凡是可以用门面调用的方法,理论上都是可以通过依赖注入来实现的
- 反过来则不一定成立, 依赖注入的类方法,却不一定可以通过门面调用,除非你自定义这个门面
2.3.2 依赖注入与服务容器之间的关系
-
服务容器: 是一个绑定了多个接口, 以及它的具体实现类的容器
-
依赖注入: 代码以接口(类型提示)做为参数,不必传入具体实现,在代码运行时,会根据配置自动从服务容器获取到接口对应的实现类,执行具体的接口方法,从而提高代码的可维护性
-
Laravel中, 所有的控制器方法(包括构造器)全部都在服务容器中解析
-
这意味着, 所以通过控制器方法传入的,可以被容器解析的接口(类型提示类)对应的服务实现类都可以被自动实例化并注入到当前控制器方法中,我们把这个自动化的过程,称之为"依赖注入"
-
最常用的当然就是
Request $request,这是典型的依赖注入模式 -
推荐开发时, 尽可能使用依赖注入来获取用户输入
-
例如,通过
$request对象获取session,cookie等
3. 资源控制器
- 在实际开发工作中, 对于控制器方法和参数的命名,很麻烦,很容易犯"命名恐惧症"
- Laravel非常人性的为开发者量身定制了一套完整的,用来定义控制器方法的一套约束规则
- 该套方法使用REST/CURD为参考,遵循RESTful, 按CURD流程来制定
- 使用以上规则创建的控制器, 就是大名鼎鼎的资源控制器
- 当你的需求比较规范和简单时,完全可以使用它来简化你的开发项目
3.1 创建资源控制器
非常简单, 只需要在创建控制器时, 添加上
--resource参数选项即可-
创建命令:
php artisan make:controller ArticleController --resource -
在
app/Http/Controllers/会自动生成控制器ArticleController.php,内容如下:
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Http\Response; class ArticleController extends Controller { /** * Display a listing of the resource. * * @return Response */ public function index() { // } /** * Show the form for creating a new resource. * * @return Response */ public function create() { // } /** * Store a newly created resource in storage. * * @param Request $request * @return Response */ public function store(Request $request) { // } /** * Display the specified resource. * * @param int $id * @return Response */ public function show($id) { // } /** * Show the form for editing the specified resource. * * @param int $id * @return Response */ public function edit($id) { // } /** * Update the specified resource in storage. * * @param Request $request * @param int $id * @return Response */ public function update(Request $request, $id) { // } /** * Remove the specified resource from storage. * * @param int $id * @return Response */ public function destroy($id) { // } }
3.2 创建资源控制器的路由
- 为简化资源控制器的路由设置, laraval为我们提供一个路由方法用来快速创建所有资源路由
- 在
web.php中添加:
// 一次性注册全部的UserControler资源控制器中的全部路由 Route::resource('article', 'ArticleController');
- 终端执行命令:
php artisan route:list, 可以查询当前的所有路由信息列表 - 控制器中的每一个方法,对应的请求类型,路由都要遵循RESTful规则,约定如下:
请求类型 路由地址 命名路由 控制器方法 描述 GET article article.index ArticleController@index 展示所有文章 POST article article.store ArticleController@store 获取表单提交数据并保存文章 GET article/create article.create ArticleController@create 发布/添加文章表单页面 GET article/{article} article.show ArticleController@show 展示单个文章 PUT/PATCH article/{article} article.update ArticleController@update 获取编辑表单输入并更新文章 DELETE article/{article} article.destroy ArticleController@destroy 删除单个文章 GET article/{article}/edit article.edit ArticleController@edit 编辑文章表单页面 - 与下面的路由定义等价的
// 展示所有文章 Route::get('article', 'ArticleController@index')->name('article.index'); // 获取表单提交数据并保存文章 Route::post('article', 'ArticleController@store')->name('article.store'); // 添加文章表单页面 Route::get('article/create', 'ArticleController@create')->name('article.create'); // 展示单个文章 Route::get('article/{article}', 'ArticleController@show')->name('article.show'); // 获取编辑表单输入并更新文章 Route::put('article/{article}', 'ArticleController@update')->name('article.update'); // 删除单个文章 Route::delete('article/{article}', 'ArticleController@destroy')->name('article.destroy'); // 编辑文章表单页面 Route::get('article/{article}/edit', 'ArticleController@edit')->name('article.edit');
- 路由举例: 渲染文章编辑页面为例
- 路由定义:
Route::get('article/{article}/edit', 'ArticleController@edit')->name('article.edit'); - 修改
ArticleController@edit方法:
public function edit($id) { return '编程文章: ' . route('article.edit', [$id]); } // route('命名路由', [参数列表]) // url('路由地址')
视图与参数
1. 视图简介
- 在日常开发过程中, 除了API接口会返回指定格式的字符串外(如JSON/XML), 大多路由返回的都是HTML文档
- 视图是MVC模式中的View部分, 大部分视图都是以HTML文档格式返回, PHP脚本也是生成HTML文档返回的
- Laravel支持三种格式的视图文档:
.php: 原生的PHP脚本.blade.php: 通过Blade模板引擎解析的PHP文档, 建议使用它,以简化模板设计.css: CSS样式表文档(了解即可)
.php,.blade.php之前学习过, 现在介绍一下css- 先创建css文档:
resources/staffs/css/style.css
body { background-color: lightblue; }
- 添加路由来解析css文档:
route/web.php
视图渲染的函数,有view()和View::make()二个,第一个之前用过
// 解释css文件 Route::get('staffs/style', function () { // return view('staffs.style'); return View::make('staffs.style'); }); // 路由仅是渲染一个视图,可以用视图路由进一步简化 Route::view('staffs/style', 'staffs.style');
2. 视图传参
- 视图传参, 可以在
view参数中实现, 也可以用视图方法with()链式传参 - 实际上之前我们已经用过视图传参了
- 在
staffsController控制器中创建一个show($id)用来显示指定ID的记录
use Illuminate\Support\Facades\DB; public function show($id) { // 获取记录 $staffs = DB::table('staffs')->where('id',$id)->first(); // 测试返回结果 dd($staffs); // 参数做为view()第二个参数, 以关联数组键值对的形式传入到模板中 return view('staffs.show', ['staffs'=>$staffs]); // 使用with()方法给模板赋值, 多个变量可使用关联数组 return view('staffs.show')->with(['staffs'=>$staffs]); // 如果只有一个变量,可以只传入变量名和值即可 return view('staffs.show')->with('staffs', $staffs); }
- 创建路由:
route/web.php
Route::get('staffs/show/{id}', 'staffsController@show');
- 视图:
resources/staffs/show.blade.php
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>显示一条记录</title> </head> <body> {{--返回满足条件和首条记录--}} {{--使用原生PHP语法输出变量--}} {!! print_r($staffs) !!} <ul> <li>ID: {{$staffs->id}}</li> <li>姓名: {{$staffs->name}}</li> <li>邮箱: {{$staffs->email}}</li> <li>工资: {{$staffs->salary}}</li> <li>手机: {{$staffs->mobile}}</li> </ul> </body> </html>引入子视图
@include('admins.public.init');Blade模板引擎
1. Blade模板引擎简介
-
php本身就是最优秀的模板语言
-
使用原生php编写的模板,高效但不简洁
-
大量的php与html代码混编,造成代码耦合度过高,易出错,难阅读与维护
-
模板引擎,即可以实现代码的简洁, 又兼顾了PHP语言的高效,非常受欢迎
-
流行的PHP模板引擎很多,如
Smarty,Twig,Plates等 -
Laravel中使用的是
Blade, 当然也你可以更换成你熟悉的模板引擎,但不推荐 -
任何一款模板引擎,都必须三个基本的功能:
- 变量显示: 包括自定义变量与系统变量
- 流程控制: 循环, 判断等
- 模板继承: 实现多个PHP文件的功能组合
-
如果模板中使用了
Blade模板引擎语法,必须将模板文件扩展名为:.blade.php -
模板文件默认位置:
resources/views -
编译后的模板文件:
storage/framework/views/ -
所谓编译: 实际就是使用原生PHP语法替换掉模板文件中的Blade指令后生成的php文件
2. 变量显示
{{ $variable }}: 显示变量(包括自定义和全局系统变量)- 显示变量,并会自动进行HTML字符的转义, 避免XSS(称跨站脚本攻击)攻击
- 对应PHP代码:
<?php echo htmlentities($variable); ?>
{!! $variable !!}: 显示原始变量值- 不会调用
htmlentities()函数过滤变量 - 对应的PHP代码:
<?php echo $variable; ?> - 适用于富文件数据的渲染(如发布文单的编辑器提交的数据)
- 不会调用
@{{ $variable }}: 原样显示, 不解析变量- 众所周知, Laravel配合的最佳前端框架是
Vue.js,而Vue.js使用{{var}}显示变量 - 为了使
Vue正确解析HTML模板,需要在它的变量语法前加上@,使它保持前端原有含义 - 此时, Blade引擎会跳过/忽略, 带有
@前缀的{{var}},并将@删除,便于Vue正常解析它
- 众所周知, Laravel配合的最佳前端框架是
- 注释:
{{-- 注释内容 --}}, 注释只会显示在Blade模板中, 不会出现最终生成的HTML文档中 @php ... @endphp: 执行原生的php代码, 有时可能会不可避免的需要写原生代码,但并不推荐
3. 流程控制
3.1 条件判断
3.1.1
@if ... @else ... @elseif ... @endif-
这是一组双标签,必须要有结束标记
-
下面经典的成绩判断为例,进行条件判断流程控制的演示:
-
创建一个视图路由:
route/web.php
// 演示模板中的流程控制 // 使用Route::view(), 第三个参数是给模板的赋值 Route::view('staffsss/demo1', 'staffsss.demo1', ['grade'=> 77]);
- 创建对应的视图/模板文档:
resources/views/staffsss/demo1.blade.php
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>流程控制</title> </head> <body> @if ($grade >= 60 && $grade < 80) <h3 style="color: blue">{{ $grade }}分, 合格</h3> @elseif ($grade >= 80 && $grade <= 100) <h3 style="color: green">{{ $grade }}分,优秀</h3> @else <h3 style="color: red">{{ $grade }}分,等着补考吧</h3> @endif </body> </html>- 编译后生成的原生语句是:
<?php if($grade >= 60 && $grade < 80): ?> <h3 style="color: blue"><?php echo e($grade); ?>分, 合格</h3> <?php elseif($grade >= 80 && $grade <= 100): ?> <h3 style="color: green"><?php echo e($grade); ?>分,优秀</h3> <?php else: ?> <h3 style="color: red"><?php echo e($grade); ?>分,等着补考吧</h3> <?php endif; ?>
-
e(), 实际上就是htmlentities(),在Laravel中的别名 -
还有二组条件指令的扩展,类似于语法糖, 用于简化if语句,你完全可以用if代替
@unless() ... @endunless, 等价@if (!condition)条件取反@isset() ... endisset, 等价于@if (isset())@empty() ... endempty, 等价于@if (empty())
3.1.2
@switch () ... @endswitch- 与原生的
switch ()结构对应, Blade还提供了@case, @break, @default配合@switch () - 将上面的案例用
@switch ()改写, 执行结果与上面案例是完全一致
@switch (true) @case ($grade >= 60 && $grade < 80) <h3 style="color: blue">{{ $grade }}分, 合格</h3> @break @case ($grade >= 80 && $grade <= 100) <h3 style="color: green">{{ $grade }}分,优秀</h3> @break @default <h3 style="color: red">{{ $grade }}分,等着补考吧</h3> @endswitch
- 编译后的原生php语句是:
<?php switch(true): case ($grade >= 60 && $grade < 80): ?> <h3 style="color: blue"><?php echo e($grade); ?>分, 合格</h3> <?php break; ?> <?php case ($grade >= 80 && $grade <= 100): ?> <h3 style="color: green"><?php echo e($grade); ?>分,优秀</h3> <?php break; ?> <?php default: ?> <h3 style="color: red"><?php echo e($grade); ?>分,等着补考吧</h3> <?php endswitch; ?>
3.2 循环结构
- 与原生循环对应, Laravel中也有三种:
@for(),@foreach(),@while() - 他们全是双标签,基本语法如下:
// for 循环 @for ($i = 0; $i < count($staffssss); $i++) {{ $talk->name }} ({{ $talk->email }} <br> @endfor // foreach 循环 @foreach ($staffssss as $staffsss) {{ $talk->name }} ({{ $talk->email }} <br> @endforeach // while 循环 @while ($item = array_pop($items)) {{ $talk->name }} ({{ $talk->email }} <br> @endwhile
- 案例, 将staffsss表中的员工记录全部循环遍历出来
- 路由如下:
route/web.php
// 通过模型取出staffsss表中所有数据 $staffssss = \App\Models\staffsssModel::all(); //dump($staffssss); // 将集合类型转为普通的数组,便于在模板中调用 $staffssss = \App\Models\staffsssModel::all()->toArray(); //dump($staffssss); //创建路由, 模板赋值 Route::view('staffsss/demo2', 'staffsss.demo2', ['staffssss'=>$staffssss]);
- 创建对应的视图/模板文档:
resources/views/staffsss/demo2.blade.php
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>流程控制</title> </head> <body> {{--@for--}} @for ($i = 0; $i < count($staffssss); $i++) <p>姓名: {{$staffssss[$i]['name']}}, 邮箱: {{$staffssss[$i]['email']}}</p> {{-- 只输出前三条记录--}} @break ($i === 2) @endfor <hr> {{--@foreach--}} @foreach ($staffssss as $key => $staffsss) {{-- @continue 跳过一些记录--}} @continue ($key > 2 && $key < 10) <p>姓名: {{$staffsss['name']}}, 工资: {{$staffsss['salary']}}, key: {{$key}}</p> @endforeach <hr> {{--@while--}} {{--设置循环条件初始值--}} @php $counter = 0; @endphp @while ($counter < count($staffssss)) <p>姓名: {{$staffssss[$counter]['name']}}, 工资: {{$staffssss[$counter]['salary']}}</p> {{-- 更新循环条件,非常重要--}} @php $counter++; @endphp @endwhile </body> </html>
4. 模板继承
- 模板继承涉及到布局文件(父视图) 和 布局实现文件(子视图)
- 父视图中主要是定义页面的基本结构和需要在子视图实现的区块
- 子视图中主要是实现父视图中定义的区块(也可引用父视图中的内容)
- 模板继承的原理,与PHP中的父类与子类之间的关系非常像,可以联想学习
4.1 布局文件(父视图)
主要涉及二个Blade指令:
@yield()和@section() / @show,语法如下:4.1.1
@yield('区块名', '默认内容')- 用于指定需要子视图继承实现的内容区块
- 第二个参数给该指令用于指定子视图未继承时的默认值
4.1.2
@section('区块名') 默认内容 @show- 功能与
@yield一样,用于指定需要子视图继承实现的内容区块 - 默认内容写在指令中间,并且可以被子视图中的
@parent指令调用
4.1.3
@yield和@section/@show区别@yield指定的默认内容对子视图不可见@section/@show指定的默认内容子视图可以通过@parent访问
4.1.4 父视图案例
父视图(布局文件位置):
resources/views/layouts/master.blade.php<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> {{-- <title>{{ $title ?? '布局文件'}}</title>--}} {{-- 用区块来定义标题,并不是好主意, @yield('名称', '默认值')--}} <title>@yield ('title', '布局模板')</title> <style> @section ('css') .container { display: flex; flex-direction: column; min-height: 800px; } .header, .footer { flex: 0 0 60px; background-color: lightblue; } .main { flex-grow: 1; margin: 5px 0; background-color: wheat; } @show </style> </head> <body> {{--使用 @yield 和 @section/@show 指令在布局文件中定义区块/插槽--}} <div class="container"> <div class="header"> {{-- @yield定义的区块: 只能覆盖, 不可扩展--}} {{-- @yield 定义的区块的默认内容,是写到第二个参数中的, 子视图无法调用--}} @yield ('header', '头部') </div> <div class="main"> {{-- @section/@show定义的区块,可覆盖, 可扩展--}} @section ('main') {{-- 默认内容--}} <h1>父模板中区块的的默认内容</h1> @show </div> <div class="footer"> @yield ('footer', '底部') </div> </div> </body> </html>4.2 继承布局文件(子视图)
主要涉及二个Blade指令:
@extends()和@section() / @endsection,语法如下:extends('父视图名称'): 指定需要继承的布局文件@section('区块')/@endsection: 实现布局文件中@yield/@section定义的区块- 子视图:
resources/views/layouts/master.blade.php
{{--子模板中只允许出现@extends, @section/@endsection 指令--}} {{虽然子视图与父视图都在layouts目录下,但仍需要指定父视图所属目录,因为系统以绝对路径方式定位视图}}} @extends('layouts.master') {{--对于title,这种非常简单的,直接传入第二个参数就可以了,类似变量赋值,没必要使用双标签--}} {{--将标题中的@yield定义的区块,替换--}} {{--@section: 传入第二个参数后, 就不再需要结束标签@endsection--}} {{--@section('title', '公司网站后台')--}} @section('header') {{-- @parent在@yield定义的区块中是无效的--}} @parent <h2>导航部分</h2> @endsection @section('main') {{-- @parent: 引用父级布局中的内容--}} @parent <ul> <li>列表1</li> <li>列表2</li> <li>列表3</li> <li>列表4</li> </ul> @endsection @section('footer') <h2>页面底部</h2> @endsection-
@extends指定的布局文件,同样必须位于resources/views目录下面,目录之间用.区隔 -
@section会依次实现布局中定义的区块,它与父视图中的@section指令,通过第一个参数,即'区块名称'建立关联 -
@section有二种语法格式:- 简化:
@section('区块', '内容'), 不需要结束标签@endsection,内容放在第二个参数中即可 - 完整:
@section('区块') 内容 @endsection,内容直接放在标签之间即可
- 简化:
-
对于子视图中没有实现的父视图区块,则直接显示原父视图中
@yield/@section中默认内容 -
对于子视图中已经实现的父视图区块,再根据区块定义方式,确定是否允许引用父视图图中的默认内容
-
对于父视图中
@yield创建的变量,如title,可使用简化语法:@section('block','value')传第二个参数实现 -
�对于父视图中的css区块
@section('css')/@show, 子视图如果没有实现, 那么就直接引用/继承了原父视图中的内容 -
当然,我们也可以在子视图中来扩展父视图的css样式, 在子视图中添加代码如下:
@section ('css') @parent .container { color: red; } @endsection
- 此时,你会看到子视图页面中的文本全部更新成了红色
- 最后再强调一下, 页面中的区块显示顺序由布局(父视图)确定, 与子视图中的实现顺序没有关系
- 也就是说,
@section('footer') <h2>页面底部</h2> @endsection最到子视图最前面也无所谓
4.3 导入组件
- 导入组件涉及二条指令:
@include(),@each() @include(): 导入单个组件, 突破继承限制,可横向扩展功能,与Trait类似@each(): 循环导入单个组件,用在需要循环遍历某个集合的场景下
4.3.1 导入单个组件/模块
- 路由:
//include导入组件测试 Route::view('include', 'include');
- 视图:
- 组件也是一个模板:
resources/views/button.blade.php
// 组件: 以添加一个课程按钮为例 <button>{{ $course }}</button>
- 创建视图
resources/views/include.blade.php - 语法:
@include('组件视图', [组件中需要用到的变量赋值])
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>导入组件</title> </head> <body> <h3>你喜欢的课程有哪些?</h3> @include('button', ['course'=>'PHP']) </body> </html>- 游览器访问:
dev.tt/include
4.3.2 循环导入多个组件/模块
- 路由:
//each循环导入组件测试 Route::view('each', 'each', ['courses'=>['php','javascript','python']]);
-
视图:
-
复用上面的课程组件:
<button>{{ $course }}</button> -
语法:
@each('组件视图', $遍历的集合变量, '循环变量', '集合为空时的视图') -
根据语法,我们应该准备二个视图/模板
-
创建集合为空的视图:
resources/views/empty.blade.php
<h3 style="color: red">空空如也~~</h3>
- 创建循环组件的视图
resources/views/each.blade.php
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>each导入组件</title> </head> <body> <h3>你喜欢的课程有哪些?</h3> @each('button', $courses, 'course', 'empty') {{--将集合变量置空再测试--}} @php $courses = []; @endphp @each('button', $courses, 'course', 'empty') </body> </html>- 游览器访问:
dev.tt/each
4.3 插槽与组件
-
Laravel 借鉴了 Vue 中的 slot 与 component 思想,使用插槽分发内容
-
涉及一个变量:
$slot和 一条指令@component -
$slot变量必须用在组件中,@component必须用在调用该组件的视图中 -
组件与插槽使得代码片断与布局,更加的语义化,也更符合思维习惯和人性化
-
下面以一下简单的JS弹窗进行演示
-
制作弹窗组件:
resources/views/alert.blade.php
<script> alert('{{ $slot }}') </script>- 创建路由:
// component 测试 Route::view('component', 'component');
- 创建路由中指定的插槽视图:
resources/views/component.blade.php - 语法:
@component('组件名称') 组件中$slot变量的值 @endcomponent
@component('alert') 验证通过 @endcomponent
- 打开游览器:
dev.tt/component,会看到一个显示'验证通过'的js弹窗
理解并掌握以上Blade模板引擎的基础知识, 相信您已经可以写出一个规范的视图模板文档了
-
数据库操作
1. 原生查询
Laravel 默认全部采用PDO实现数据库的CURD操作
参数占位符,支持匿名(?)与命名二种语法(:name)
约定,全部采用闭包路由演示(web.php)1.1 原生读/查询操作
select($sql, [参数列表]): 获取满足条件的记录, 返回stdClass对象数组
// 获取指定ID的记录 Route::get('db/select/{n?}', function ($n=1){ $res = DB::select('SELECT `id`,`name`,`email` FROM `staffs` LIMIT 0, :n', ['n'=>$n]); // 返回只有一个对象元素的数组 var_dump($res); // 格式化打印 echo '<pre>'. var_export($res,true); // 因为这种格式非常的友好, Laravel给我们封装一个方法dd()/dump()更加优雅的实现了这个功能 dump($res); dd($res); // dd() = dump() + die, 输出结果后立即终止代码执行 });
- 这种结果集的显示效果是如何实现的呢, 我们来模拟一下
- 分析, 每一个对象元素,使用JSON格式的对象字面量表示,而PHP中并没有对象字面量的语法
- 而PHP中并没有像JS,Python一样的对象字面量的语法,所以需转换,使用函数
json_encode() json_encode(): 将对象/数组转为JSON格式字符串
// 将对象数组中的每一个元素(对象)转为JSON字符串 // 1. 获取到结果数组中每一个元素的值,并组成一个新数组返回 $values = array_values($res); // 2. 遍历处理每一个值,转换为JSON字符串 $jsonArr = array_map(function($value){ return json_encode($value); }, $values); // 3. 现在看上去就和dump/dd()显示基本一致(除了样式不同) print_r($jsonArr);
1.2 原生更新操作
DB::update($sql, [参数列表]): 更新记录, 返回更新行数
// 更新指定ID的记录: 仅更新年龄age和工资salary字段 Route::get('db/update/{id}', function ($id) { // 被更新字段的值, 应该来自表单中POST变量, 这里用字面量进行模拟 $_POST['age'] = 30; $_POST['salary'] = 8488; list($age, $salary) = [$_POST['age'], $_POST['salary']]; // 参数使用匿名占位符: ? 问号 $num = DB::update('UPDATE `staffs` SET `age`= ?, `salary`= ? WHERE `id` = ? ', [$age, $salary, $id]); // $res = DB::update('UPDATE `staffs` SET `age`= 20 WHERE `id` = :id', ['id'=>$id]); return '成功的更新了: ' . $num . ' 条记录'; })->where('id', '\d+');
1.3 原生新增操作
DB::insert($sql, $data): 新增记录, 成功返回true
Route::get('db/insert', function () { // 新增的数据,实际开发中应该来自用户提交的表单 $data = [ 'name'=> '东方不败', 'sex'=>1, 'age'=>30, 'salary'=>8000, 'email'=>'dfbb@php.cn', 'mobile'=> '15800448811', 'password'=>sha1('123456'), 'created_at'=>time(), 'updated_at'=>time(), ]; // 字段列表 $fields = ' (`name`,`sex`,`age`,`salary`,`email`,`mobile`,`password`,`created_at`, `updated_at`) '; // 值列表: 采用命名占位符 $values = ' (:name, :sex, :age, :salary, :email, :mobile, :password, :created_at, :updated_at) '; // SQL语句 $sql = 'INSERT `staffs` ' . $fields . ' VALUES ' . $values; // 执行新增操作, 成功则返回受影响的记录数,即新增数量 $num = DB::insert($sql, $data); return '成功的新增了: ' . $num . ' 条记录'; });
1.4 原生删除操作
DB::delete($sql, $where): 删除记录, 返回受影响的行数
Route::get('db/delete/{id?}', function ($id = null) { if (is_null($id)) return '条件不能为空'; else $num = DB::delete('DELETE FROM `staffs` WHERE `id` = ?', [$id]); return '成功的删除了: '. $num . ' 条记录'; })->where('id', '\d+');
2. 查询构造器
- 查询构造器, 是用户与数据库交互的重要工具,是数据库功能的底层核心组件
- 采用面向对象方式,基于链式方法调用完成, 模型也直接或间接依赖于它
- 基于
DB门面类,调用table()构造一个基于指定数据表的构造构建器
2.1 查询记录
get(columns|*): 获取满足条件的所有记录,参数是字段列表(可省),返回Collectionfirst(columns|*): 获取满足条件的首条记录,参数是字段列表(可省), 返回单个stdClassvalue(column): 获取某一列的当前值,返回单值pluck(value, key): 获取指定列值与对应主键组成的关联数组(很实用的功能)chunk(n,callback): 分批/块处理超大结果集,防止内存爆仓, 返回布尔值
Route::get('db/get', function () { // get(): 获取满足条件的所有记录,可指定字段 $res = DB::table('staffs')->get('*'); $res = DB::table('staffs')->get(['id', 'name', 'email']); // first(): 获取满足条件的首条记录,返回单个对象stdClass $res = DB::table('staffs')->first(); $res = DB::table('staffs')->first(['id', 'name', 'email']); // value(column): 获取某一列的当前值,返回单值 $res = DB::table('staffs')->value('name'); // pluck(v,k): 获取指定列值与对应主键组成的关联数组, 返回数组 $res = DB::table('staffs')->pluck('name','id'); // chunk(n,callback): 分批/块处理超大结果集,防止内存爆仓, 返回布尔值 $res = []; // 通常使用外部变量将结果返回, 本例返回二维数组 // 必须先将表中记录根据主键排序后, 分块将数据以对象数组方式注入到回调参数$staffss中 // 回调以引用的方式使用了外部变量$res, 所以,函数中对$res的更新将会实时映射到原数组中 $res = DB::table('staffs')->orderBy('id')->chunk(5, function ($staffss) use (&$res){ foreach ($staffss as $staffs) { $res[] = ['name'=>$staffs->name, 'email'=>$staffs->email]; } }); dump($res); }
2.2 新增记录
insert(array $data): 新增记录,返回新增的记录数量insertGetId(array $data): 新增记录,关返回新增记录的主键ID
// 新增一条记录,数据以一维数组方式提供 $data = [ 'name'=> '东方不败', 'sex'=>1, 'age'=>30, 'salary'=>8000, 'email'=>'dfbb@php.cn', 'mobile'=> '15800448811', 'password'=>sha1('123456'), 'created_at'=>time(), 'updated_at'=>time(), ]; Route::get('db/add', function () use ($data) { // 新增一条记录 $num = DB::table('staffs')->insert($data); //return '成功新增了: ' .$num . ' 条记录'; // 获取新增记录的主键ID $id = DB::table('staffs')->insertGetId($data); return '新增记录的主键ID是: ' . $id; });
- 如果想一次新增多条记录, 传入一个二维数组即可
- 出错返回
QueryException, 多条插入时, 会整体中断, 一条也不会添加成功
2.3 更新记录
update(array $data): 更新记录时, 必须要设置更新条件where(): 更新条件, 更多设置技巧 , 在后面课程中再说, 这里仅用到等值查询- ``: 自增自减,默认步长为1, 仅针对整数型字段有意义
Route::get('db/save/{id}', function ($id) { $num = DB::table('staffs')->where('id', $id)->update(['salary'=>1989]); return '成功更新了: ' .$num . ' 条记录'; });
2.4 删除记录
delete(): 删除记录, 与更新一样, 也必须设置条件
Route::get('db/del/{id}', function ($id) { $num = DB::table('staffs')->where('id', $id)->delete(); return '成功删除了: ' . $num . ' 条记录'; });
2.5 聚合查询
- 主要对记录进行统计,或者对数值字段进行求和,求平均值,以及最大/最小值等
- 聚合查询的返回都是数值型
- 涉及方法有:
count(),sum(),avg(),min(),max()
$num = DB::
3. select()查询
3.1 字段封装
select(columns): 设置查询字段名称- 参数是字符串列表:
select('id','name','email') - 参数是字符串数组:
select(['id','name','email'])
3.2 原始列名
selectRaw(string): 直接写SQL语句中的原始列名selectRaw('id,name,salary')
Route::get('db/column', function () { $res = DB::table('staffs')->select('id', 'name','email')->get(); $res = DB::table('staffs')->select('id', 'name as 姓名','email as 邮箱')->get(); $res = DB::table('staffs')->select(['id', 'name','email'=>'工资'])->get(); $res = DB::table('staffs')->selectRaw('`id`,`name`, `salary`')->get(); $res = DB::table('staffs')->selectRaw('`id`,`name`, `salary` as `工资`')->get(); dump($res); });
4. where() 简单查询
4.1 表达式查询
- 语法:
where(字段, 操作符, 值), 如where('id', '>', 5) - 等值查询可省略操作符:
where(字段, 值), 如where('id', 2)
4.2 区间查询
- 语法:
whereBetween(字段, [起始, 结束]) - SQL:
BETWEEN start AND end
4.3 集合查询
- 语法:
whereIn(字段, [值1,值2, ...]) - SQL:
WHERE field IN (value1, value2,....)
4.4 AND 查询
- 语法:
where([...]) - SQL:
WHERE condition1 AND condition2
4.5 OR 查询
- 语法:
orWhere() - SQL:
WHERE condition1 OR condition2
4.6 原始条件查询
- 语法:
whereRaw(SQL)
4.7 闭包查询
- 语法:
where(function ($query){...}) - 闭包中直接使用
$query调用查询方法, 不需要返回结果
4.8 其它方法
toSql(): 查看生成的SQL语句模板toArray(): 将结果对象集合转为数组
Route::get('db/where', function (){ // 1. 表达式查询: where(字段, 操作符, 值) $res = DB::table('staffs')->where('id', '=', 5)->pluck('name', 'id'); // 等值查询,中间的等号可以省略 $res = DB::table('staffs')->where('id', 5)->pluck('name', 'id'); $res = DB::table('staffs')->where('age', '>', 35)->pluck('name','id'); // 2. 模糊查询 $res = DB::table('staffs')->where('name', 'like', '%大%')->pluck('name','id'); // 3. 区间查询,还有一个whereNotBetween(),与之功能相反 $res = DB::table('staffs')->whereBetween('age', [30,40])->pluck('name','id'); // 4. 集合查询 $res = DB::table('staffs')->whereIn('id', [1,3,5])->pluck('name', 'id'); // 5. AND 查询, "与",只有满足所有条件才会匹配到 // `age` < 50 AND `salary` < 5000 $res = DB::table('staffs') ->where('age','<', 50) ->where('salary', '<', 5000) ->pluck('name','id'); // 可以将多个条件以数组参数形式传给where() $res = DB::table('staffs')->where([ ['age','<', 50], ['salary', '<', 5000] ])->pluck('name','id'); // 6. OR 查询 , "或", 只要满足一个条件就会匹配到 // `age` < 30 OR `salary` < 5000 $res = DB::table('staffs') ->where('age','<', 30) ->orWhere('salary', '<', 5000) ->pluck('name','id'); // 7. 原生条件查询 // toSql(): 返回生成的SQL语句 // 使用原生表达式时, 字段名或关键字推荐加上反引号, 防止命名冲突 $res = DB::table('staffs')->whereRaw('`age` < 30 AND `salary` > 5000')->toSql(); // dd($res); $res = DB::table('staffs')->whereRaw('`age` < 30 AND `salary` > 5000')->pluck('name', 'id'); // 设置结果集中的字段的方法select(),也有类似的方法 $res = DB::table('staffs')->whereRaw('`age` < 30 AND `salary` > 5000') ->selectRaw('`id`,`name`,`age`, `salary`') ->get(); // 8. 闭包查询: 例如查询年龄小于40,且 工资在5000到8000之间的 的 男性员工信息 // SELECT * FROM `staffs` WHERE `age` < 40 AND (`salary` BETWEEN 5000 AND 8000 ) AND `sex` =1 // 对于这样的查询任务, 使用前面的语法实现起来比较繁琐,而使用闭包查询则非常直观方便 $res = DB::table('staffs')->where('age', '<', 40) ->where(function (Illuminate\Database\Query\Builder $query){ $query->whereBetween('salary', [5000, 8000]) ->where('sex', 1); })->get(); // 查看结果 dump($res->toArray()); });
5. where()连接查询
5.1 内连接
5.1.1 传统多表查询
-
通过在查询中设置连接条件的方式,来移除结果集中某些数据行后的交叉连接
-
通俗讲: 利用条件表达式来消除交叉连接的某些行
-
换句话说, 就是在交叉连接(迪卡尔乘积)的结果集中返回满足条件的记录
-
内连接与传统的多表查询是类似的, 例如有二张表,通过
id关联
-- 员工表staffs(id,name,salary...), 客户表custom(id,name,sid) -- staffs.id 与 custom.sid 关联 -- 将客户表中的名称与对应的员工关联,典型的多表等值查询 SELECT `custom`.`id`, `custom`.`name`, `staffs`.`name` FROM `staffs`, `custom` WHERE `custom`.`sid` = `staffs`.`id`
5.1.2 内连接多表查询
-
内连接:
INNER JOIN: 连接二张表ON: 设置连接条件
-
将以上SQL语句改为内连接查询, 需要更新二处:
FROM后二张表之间用:INNER JOIN连接WHERE换成:ON(不换成ON也不报错)
SELECT `custom`.`id`, `custom`.`name`, `staffs`.`name` FROM `staffs` INNER JOIN `custom` ON `custom`.`sid` = `staffs`.`id`
- 尽管以上二种语法都可以实现多表查询,但内连接更加直观高效,推荐使用
- 内连接是SQL默认的连接方式,所以
INNER关键字可省略,只用JOIN即可 - 内连接最常用于二张表,但也适合于二张以上的表执行查询
5.2 外连接
与内连接在是交叉集合中返回匹配记录不同, 外连接更注意二张表之间的关系
外连接中的表之间的顺序会影响到查询结果
所以按照连接表的顺序,可以分为: 左外连接 和 右外连接 二类5.2.1 术语
- 基表: 由它发起连接请求, 返回结果的行数与它一致
- 参考表: 与基表中的条件进行匹配, 不匹配的返回NULL
- 所以, 根据基表在左边还是右边, 分为"左连接"和"右连接"二种形式
5.2.2 左外连接
- 左边是基表, 右边是参考表
- 以左边为基表进行查询, 返回的是左表中的所有行
- 结果集中, 除了匹配的行之外, 还包括左表中存在但与右边中不匹配的行
- 未匹配的行中, 对应列设置为空值
NULL, 表示右边中这些行没有与左表中匹配的行 - 表之间使用连接关键字:
LEFT OUTER JOIN或者LEFT JOIN
-- 以左边custom为基表, 右边staffs员工表为参考表, 查询每个客户对应的负责的员工 -- 凡是与客户表无对应的员工, 全部在结果中返回null, null值出现在左边 SELECT `custom`.`id`, `custom`.`name`, `staffs`.`name` FROM `staffs` LEFT JOIN `custom` ON `custom`.`sid` = `staffs`.`id`
5.2.3 右外连接
- 右边是基表, 左表是参考表
- 以右表为基表进行查询, 返回的是右表中的所有行
- 与左连接一样, 未匹配的行,对应的列设置为NULL值
- 使用的连接关键字:
RIGHT OUTER JOIN或者RIGHT JOIN
SELECT `custom`.`id`, `custom`.`name`, `staffs`.`name` FROM `staffs` RIGHT JOIN `custom` ON `custom`.`sid` = `staffs`.`id`
5.2.4 交叉连接
- 又叫迪卡尔乘积连接, 不设置where条件返回左边行数乘以右边行数之积
SELECT `custom`.`id`, `custom`.`name`, `staff`.`name` FROM `staff` CROSS JOIN `custom`
- 如果设置条件, 返回结果与前面的内连接等值查询完全相同
- 所以, 内连接就是在交叉连接的结果集中选取匹配的记录
--带条件 SELECT `custom`.`id`, `custom`.`name`, `staff`.`name` FROM `staff` CROSS JOIN `custom` WHERE `custom`.`sid` = `staff`.`id`;
5.2.4 小提示
- 左/右连接,最终返回的结果集记录数量与以哪个为基表相关
- 匹配到行, 对应的列有值, 未匹配到行, 对应的列为NULL值
- 条件交叉连接, 其实就是内连接
5.3 Laravl中的实现
- 内连接:
join(参考表, 参考表字段, 操作符, 基表字段) - 外左连接:
leftJoin(参考表, 参考表字段, 操作符, 基表字段) - 外右连接:
rightJoin(参考表, 参考表字段, 操作符, 基表字段) - 交叉连接:
crossJoin(参考表, 参考表字段, 操作符, 基表字段)
// 连接查询 Route::get('db/join', function (){ // 内连接 $res = DB::table('custom') ->join('staffs', 'staffs.id', '=' ,'custom.sid') ->select('custom.id', 'custom.name as custom_name', 'staffs.name', 'staffs.mobile') ->get(); // 左连接 // 返回左表custom匹配到的所有行,staffs未匹配行的字段为NULL值 $res = DB::table('custom') ->leftJoin('staffs', 'staffs.id', '=' ,'custom.sid') ->select('custom.id', 'custom.name as custom_name', 'staffs.name', 'staffs.mobile') ->get(); // 右连接 // 返回右表staffs匹配到的所有行, custom未匹配行的字段为NULL值 $res = DB::table('custom') ->rightJoin('staffs', 'staffs.id', '=' ,'custom.sid') ->select('custom.id', 'custom.name as custom_name', 'staffs.name', 'staffs.mobile') ->get(); // 交叉连接(无条件, 返回二张表行数乘积6*13=78, 也叫迪卡尔乘积) $res = DB::table('custom') ->crossJoin('staffs') ->select('custom.id', 'custom.name as custom_name', 'staffs.name', 'staffs.mobile') ->get(); // 交叉连接(有条件, 从交叉结果集是筛选匹配条件的行, 也内连接查询完全相同) $res = DB::table('custom') ->crossJoin('staffs','staffs.id', '=' ,'custom.sid') ->select('custom.id', 'custom.name as custom_name', 'staffs.name', 'staffs.mobile') ->get(); // 查看结果 dump($res); });
下节课, 我们一起来学习激动人心的
Eloquent模型操作数据表Eloquent模型
1. Eloquent 是什么
1.1 ORM与ActiveRecord的关系
Eloquent: 是一个 "ActiveRecord ORM" 框架ORM: 对象关系映射模型(Object Relational Mapping)ORM, 主要用来构建数据表与类之间的映射关系,实现以编程方式处理数据库ActiveRecord是ORM的一种实现方案Eloquent则是Laravel版的ActiveRecord
1.2 Eloquent与数据表的关系
- 一个
Eloquent模型类会映射到一张数据表 - 模型类的实例, 通常会有二个主要的功能:
- 与表中的一条具体记录对应, 用来操作该记录
- 用来调用模型中的方法, 对数据表进行操作(如CURD)
- 使用模型操作数据表时, 遵循"约定优于配置"原则

Laravel源码剖析
源码剖析我们将从如下步骤逐步分析和实练,从而掌握基于Laravel的项目开发: