Open suhao opened 2 years ago
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为.envenv()
调用:主要在config/
目录下面的配置文件中进行调用, 如config/app.php
在.env
中可以设置数据库连接的相关参数:
DB_CONNECTION=mysql
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=blog
DB_USERNAME=root
DB_PASSWORD=root
可以使用Adminer
来连接数据库,并创建相关的数据表和导入数据等。
Laravel是基于MVC架构的开源PHP框架
Laravel中的所有Web访问,必须设置路由才会生效
对php来说,一个页面,对应一个php脚本文件,以dev.tt/index.php为例:
routes/web.php
dev.tt/index.php
:一个页面, 对应的就是一个应用程序
dev.tt/index.php
, 就可以看到输出的HTML内容app/Http/Controllers
目录下面App\Http\Controllers
)与它的访问路径是一致的, 便于自动加载app\Http\Controllers.php
,app
目录下面app
目录下, 可以直接创建一个模型类, 不过, 更推荐创建一个模型目录app/models
统一管理resources/views
.blade.php
sven/artisan
实现中间件也是一个类文件, 主要有创建, 注册, 触发三个阶段
artisan
命令方式创建app/Http/kernel.php
内核文件中完成routes/web.php
,或者在控制器的构造方法中触发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);
}
}
中间件(以全局为例)的注册完全在app/Http/kernel.php
中, 需要分二步完成:
protected $middleware=[...]
数组中protected $routeMiddleware = [...]
数组protected $middleware = [
...
// 注册自定义的中间件
\App\Http\Middleware\CheckNameMiddleware::class,
];
protected $routeMiddleware = [
...
// 注册自定义中间件的路由
'checkName' => \App\Http\Middleware\CheckNameMiddleware::class,
];
中间件涉及到请求, 控制器,视图等, 为了简化, 我们只在路由中进行测试: routes/web.php
// 测试中间件, 为简化直接用闭包实现, 不做视图了
// 这是中间件验证失败后的跳转路由
Route::get('login', function () {
return '<h1>登录页面</h1>';
});
// 中间件验证路由
Route::get('home/{name}', function ($name) {
return '<h3>欢迎用户'.$name. '回来</h3>';
})->middleware('checkName');
Route
这个Facade
类来声明的Route::get('hello', funciton (){...})
web.php
: 主要处理浏览器发起的URL请求, 这是我们学习的主要对象api.php
: 主要用于API接口请求,通常用在跨语言, 跨应用的环境中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 () {...});
RESTful
规范{...}
包裹,多个参数之间使用/
分隔,如add/{x}/{y}
在routes/web.php
中, 定义一个闭包路由进行演示
Route::get('sum/{a}/{b}', function ($a, $b) {
return "$a + $b = " . ($a + $b);
});
?
号即可,如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
where()
方法,对参数类型进行约束, 可以使用正则做为条件\d+
, \w+
Route::get('sum/{a}/{b}', function ($a, $b) {
return "$a + $b = " . ($a + $b);
})->where('a', '\d+')->where('b', '\d+');
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');
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>';
});
// 执行结果与上面是完全一样的
echo
,应该统一使用return
, 由响应对象决定如何返回前端Route::get('dev', function () { return 'Hello dev.tt';});
Route::get('dev', 'hello');
function hello { return 'Hello dev.tt'; }
app\Http\Controllers
目录下面@
符区隔开, 如DemoController@index
namespace App\Http\Controllers;
class DemoController extends Controller
{
public function index()
{
//...
}
}
Route::any('demo', 'DemoController@index')
;从模型类获取数据,通过控制器,渲染到视图页面中
从视图页面中获取用户提交的请求/数据:
控制器不应该承载太多的功能,他更适合充当路由请求的对象
控制器并非应用访问的唯一入口,例如Artisan, 队列,任务调度等
过度依赖控制器会带来以后应用扩展的麻烦,所以,控制器的功能应该专业,单一
命名空间: 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语法
<!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');
以添加一个员工数据为例
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');
}
}
Session
, Cookie
等,但并不是所有类都是门面接口服务容器: 是一个绑定了多个接口, 以及它的具体实现类的容器
依赖注入: 代码以接口(类型提示)做为参数,不必传入具体实现,在代码运行时,会根据配置自动从服务容器获取到接口对应的实现类,执行具体的接口方法,从而提高代码的可维护性
Laravel中, 所有的控制器方法(包括构造器)全部都在服务容器中解析
这意味着, 所以通过控制器方法传入的,可以被容器解析的接口(类型提示类)对应的服务实现类都可以被自动实例化并注入到当前控制器方法中,我们把这个自动化的过程,称之为"依赖注入"
最常用的当然就是Request $request
,这是典型的依赖注入模式
推荐开发时, 尽可能使用依赖注入来获取用户输入
例如,通过$request
对象获取session
,cookie
等
非常简单, 只需要在创建控制器时, 添加上--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)
{
//
}
}
web.php
中添加:// 一次性注册全部的UserControler资源控制器中的全部路由
Route::resource('article', 'ArticleController');
php artisan route:list
, 可以查询当前的所有路由信息列表请求类型 | 路由地址 | 命名路由 | 控制器方法 | 描述 |
---|---|---|---|---|
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('路由地址')
.php
: 原生的PHP脚本.blade.php
: 通过Blade模板引擎解析的PHP文档, 建议使用它,以简化模板设计.css
: CSS样式表文档(了解即可).php
, .blade.php
之前学习过, 现在介绍一下cssresources/staffs/css/style.css
body {
background-color: lightblue;
}
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');
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');
php本身就是最优秀的模板语言
使用原生php编写的模板,高效但不简洁
大量的php与html代码混编,造成代码耦合度过高,易出错,难阅读与维护
模板引擎,即可以实现代码的简洁, 又兼顾了PHP语言的高效,非常受欢迎
流行的PHP模板引擎很多,如Smarty
, Twig
, Plates
等
Laravel中使用的是Blade
, 当然也你可以更换成你熟悉的模板引擎,但不推荐
任何一款模板引擎,都必须三个基本的功能:
如果模板中使用了Blade
模板引擎语法,必须将模板文件扩展名为: .blade.php
模板文件默认位置: resources/views
编译后的模板文件: storage/framework/views/
所谓编译: 实际就是使用原生PHP语法替换掉模板文件中的Blade指令后生成的php文件
{{ $variable }}
: 显示变量(包括自定义和全局系统变量)
<?php echo htmlentities($variable); ?>
{!! $variable !!}
: 显示原始变量值
htmlentities()
函数过滤变量<?php echo $variable; ?>
@{{ $variable }}
: 原样显示, 不解析变量
Vue.js
,而Vue.js
使用{{var}}
显示变量Vue
正确解析HTML模板,需要在它的变量语法前加上@
,使它保持前端原有含义@
前缀的{{var}}
,并将@
删除,便于Vue
正常解析它{{-- 注释内容 --}}
, 注释只会显示在Blade模板中, 不会出现最终生成的HTML文档中@php ... @endphp
: 执行原生的php代码, 有时可能会不可避免的需要写原生代码,但并不推荐@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())
@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 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; ?>
@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
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>
主要涉及二个Blade指令: @yield()
和 @section() / @show
,语法如下:
@yield('区块名', '默认内容')
@section('区块名') 默认内容 @show
@yield
一样,用于指定需要子视图继承实现的内容区块@parent
指令调用@yield
和@section/@show
区别@yield
指定的默认内容对子视图不可见@section/@show
指定的默认内容子视图可以通过 @parent
访问父视图(布局文件位置): 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>
主要涉及二个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
最到子视图最前面也无所谓@include()
, @each()
@include()
: 导入单个组件, 突破继承限制,可横向扩展功能,与Trait
类似@each()
: 循环导入单个组件,用在需要循环遍历某个集合的场景下//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
//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
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模板引擎的基础知识, 相信您已经可以写出一个规范的视图模板文档了
Laravel 默认全部采用PDO实现数据库的CURD操作 参数占位符,支持匿名(?)与命名二种语法(:name) 约定,全部采用闭包路由演示(
web.php
)
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_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);
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+');
DB::insert($sql, $data)
: 新增记录, 成功返回trueRoute::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 . ' 条记录';
});
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+');
- 查询构造器, 是用户与数据库交互的重要工具,是数据库功能的底层核心组件
- 采用面向对象方式,基于链式方法调用完成, 模型也直接或间接依赖于它
- 基于
DB
门面类,调用table()
构造一个基于指定数据表的构造构建器
get(columns|*)
: 获取满足条件的所有记录,参数是字段列表(可省),返回Collection
first(columns|*)
: 获取满足条件的首条记录,参数是字段列表(可省), 返回单个stdClass
value(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);
}
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
, 多条插入时, 会整体中断, 一条也不会添加成功update(array $data)
: 更新记录时, 必须要设置更新条件where()
: 更新条件, 更多设置技巧 , 在后面课程中再说, 这里仅用到等值查询Route::get('db/save/{id}', function ($id) {
$num = DB::table('staffs')->where('id', $id)->update(['salary'=>1989]);
return '成功更新了: ' .$num . ' 条记录';
});
delete()
: 删除记录, 与更新一样, 也必须设置条件Route::get('db/del/{id}', function ($id) {
$num = DB::table('staffs')->where('id', $id)->delete();
return '成功删除了: ' . $num . ' 条记录';
});
count()
,sum()
,avg()
, min()
, max()
$num = DB::
select(columns)
: 设置查询字段名称select('id','name','email')
select(['id','name','email'])
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);
});
where(字段, 操作符, 值)
, 如where('id', '>', 5)
where(字段, 值)
, 如where('id', 2)
whereBetween(字段, [起始, 结束])
BETWEEN start AND end
whereIn(字段, [值1,值2, ...])
WHERE field IN (value1, value2,....)
where([...])
WHERE condition1 AND condition2
orWhere()
WHERE condition1 OR condition2
whereRaw(SQL)
where(function ($query){...})
$query
调用查询方法, 不需要返回结果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());
});
通过在查询中设置连接条件的方式,来移除结果集中某些数据行后的交叉连接
通俗讲: 利用条件表达式来消除交叉连接的某些行
换句话说, 就是在交叉连接(迪卡尔乘积)的结果集中返回满足条件的记录
内连接与传统的多表查询是类似的, 例如有二张表,通过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`
内连接:
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`
INNER
关键字可省略,只用JOIN
即可与内连接在是交叉集合中返回匹配记录不同, 外连接更注意二张表之间的关系 外连接中的表之间的顺序会影响到查询结果 所以按照连接表的顺序,可以分为: 左外连接 和 右外连接 二类
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`
RIGHT OUTER JOIN
或者 RIGHT JOIN
SELECT `custom`.`id`, `custom`.`name`, `staffs`.`name`
FROM `staffs`
RIGHT JOIN `custom` ON `custom`.`sid` = `staffs`.`id`
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`;
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
: 是一个 "ActiveRecord ORM" 框架ORM
: 对象关系映射模型(Object Relational Mapping)ORM
, 主要用来构建数据表与类之间的映射关系,实现以编程方式处理数据库ActiveRecord
是ORM
的一种实现方案Eloquent
则是Laravel版的ActiveRecord
Eloquent
模型类会映射到一张数据表
Laravel源码剖析
源码剖析我们将从如下步骤逐步分析和实练,从而掌握基于Laravel的项目开发: