本文档前言
Laravel 文档写的很好,只是新手看起来会有点吃力,需要结合经验和网上的文章,多读、细读才能更好的理解。Again,多读、细读官方文档。本文类似于一个大纲,欲知其中详情,且去细读官方文档:Laravel 5.5 docs。
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
第一章:前言(一、版本控制方案。二、Laravel 5.5 新特性。三、贡献指南。四、api 文档。)
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
一、版本控制方案
- 安装版本包时应当指定主版本号和次版本号,如:
5.5.*
。 - Laravel 5.5 是 LTS 版本,提供两年的 bug 修复和三年的安全修复。非 LTS 版本,只提供六个月的 bug 修复和一年的安全修复。
二、Laravel 5.5 的新特性(简单介绍)
Horizon ( redis 队列配置仪表盘)、包自动发现、api 资源、控制台命令自动注册、队列任务链、队列任务速率控制、基于时间的任务尝试、自定义的验证规则、可信任的代理集成、按需通知、可渲染的邮件、自定义可报告可渲染的异常、请求对象中的认证方法、一致的异常处理、缓存锁、读写数据库配置 sticky 选项。
三、贡献指南
本节主要指示用户在提出增加框架新特性或者提交框架使用反馈时,需要做些什么、怎么做。
四、api 文档
官方 api 文档地址:https://laravel.com/api/5.5/
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
第二章:开始(一、安装。二、配置。三、维护模式。四、目录结构。五、homestead。六、部署。)
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
一、安装
两种安装方式
composer global require "laravel/installer"
laravel new blog
// 无法指定版本composer create-project --prefer-dist laravel/laravel blog "5.5.*"
安装须知
- 根目录是
public
- 必须设置 web 服务器可读写
storage
和bootstrap/cache
目录及其子目录 - 在 web 服务器配置中设置优雅链接 (即去除
index.php
)
二、配置
环境配置
.env
文件内的变量会被系统级别或服务器级别的变量覆盖。.env
文件内的变量通过env()
函数获取,config
目录下的变量通过config()
函数获取。- 在运行
PHPUnit
测试时或执行以--env=testing
为选项Artisan
命令时,.env.testing
会覆盖.env
文件中的值。
操作配置、缓存配置
$value = config('app.timezone'); // 获取
config(['app.timezone' => 'America/Chicago']); // 设置
php artisan config:cache // 缓存(执行该命令后 env 函数将会失效)
php artisan config:clear // 清除
三、维护模式
维护模式 === 503 === Service Unavailable
php artisan down
php artisan down --message="Upgrading Database" --retry=60
// 优先加载:resources/views/errors/503.blade.php
# 尝试第二条命令的时候,你会发现并不能生效,一开始我也以为是个 bug,
# 为此我还在 github 上 pull request
# 官方解释说你要自建模板 resources/views/errors/503.blade.php 去实现任何你想实现的功能。
# 我觉得这边做的不是很好,既然有这个命令选项,就应该实现对应的功能,
# 否则新手根据文档学习到的时候,会困惑为什么该指令的参数不能生效。
当应用程序处于维护模式时,不会处理队列任务。退出维护模式后会继续处理。
四、目录结构
根目录下的各个目录
app
:应用程序核心目录,几乎项目所有的类都在这里。bootstrap
:包含框架启动文件app.php
,和启动时为了优化性能而生成的文件。config
:包含所有配置文件。最好是读一遍这些文件,了解你可以轻松配置哪些内容。database
:包含数据库填充、迁移、模型工厂文件。可以用作SQLite
数据库存放目录。public
:静态资源目录,并包含了首页文件index.php
。resource
:包含了未编译的源文件(模板、语言、资源)。routes
:包含了所有的路由定义。storage
:包含了编译好的模板文件,session 文件,缓存文件,日志等文件。tests
:包含了自动测试文件。运行测试命令php vendor/bin/phpunit
。vendor
:composer
依赖目录。
app
目录下的各个目录
app 目录下的很多目录是命令生成的。查看生成命令:php artisan make:list
。
Console
:包含自定义的命令和用来注册命令、定义计划任务的内核文件。Events
:事件目录。Exceptions
:异常和异常处理目录。Http
:包含了控制器、中间件和表单请求。几乎所有请求的处理逻辑都被放在这里。Jobs
:储存队列任务。Listeners
:存储事件的监听。Mail
:存储邮件类目录。Notifications
:存放通知类。laravel 内置了很多驱动: email, Slack, SMS, database。Policies
:授权策略类目录。Providers
:包含了所有服务提供者。服务提供者通过在服务容器上绑定服务、注册事件或者执行其他任务来启动你的应用。Rules
:储存自定义验证规则对象。
routes
目录下的各个目录
web.php
内的路由将应用web
中间件组(功能:session 状态,CSRF 保护,cookie 加密等)。api.php
内的路由将应用api
中间件组(功能:访问速率控制等)。所有的请求将通过令牌认证,无 session 状态。consoles.php
定义了所有基于控制台命令的闭包。channels.php
注册了所有的事件广播频道。
五、Homestead
本节介绍了 homestead
是什么,怎么用。
介绍
Homestead 是 Laravel 官方提供的 vagrant box。
vagrant 一个用来给虚拟机提供物品(box)的容器,放在容器(vagrant)里的东西被称作 box 。box 一般就是一个操作系统的镜像。
Homestead 提供了 ubantu、git、php、nginx、apache、mysql、mariadb、sqlite3、postgreSQL、composer、node(With Yarn, Bower, Grunt, and Gulp)、redis、memcached、beanstalkd、mailhog、elasticsearch、ngrok。
windows 需要在 BIOS 中开启硬件虚拟功能 (VT-x)。如果在 UEFI 上使用 Hyper-V,为了使用 VT-x 需要禁用 Hyper-V。
安装
- 安装虚拟机和 vagrant。
(建议使用 virtualbox,vmware 收费并需要购买插件,parallels 也需要插件,Hyper-V 有局限性)
vagrant 下载页
virtualbox 下载页 往 vagrant 里面放
laravel/homestead
box。
vagrant box add laravel/homestead
国内墙的厉害,需要下载下来再进行本地安装:
https://vagrantcloud.com/laravel/boxes/homestead/versions/x.x.x/providers/virtualbox.box
在上面链接中找到想要下载的版本,然后替换掉 x.x.x ,复制到迅雷中进行下载。
重命名为 larabox
vagrant box add laravel/homestead ./larabox
克隆仓库 laravel/homestead。(用来初始化 vagrant,配置 homestead box 的程序。)
a.git clone https://github.com/laravel/homestead.git ~/Homestead
b.cd ~/Homestead
c.git checkout v7.1.2
(切换到稳定版本)
d.bash init.sh
或者init.bat
(生成Homestead.yaml
文件)
配置(配置文件 Homestead.yaml)
指定虚拟机
可以是 virtualbox, vmware_fusion, vmware_workstation, parallels 或者 hyperv
provider: virtualbox
共享文件夹
folders
属性用于配置本地和homestead
环境的同步文件夹。
由于多站点或项目有太多文件而导致性能变差时候,可以设置不同的文件夹来同步不同的项目。
NFS
也可以用来优化性能,windows 不支持。使用NFS
时,安装vagrant-bindfs
插件可维护正确的文件和文件夹权限。
利用 options
键可以设置其他选项。
nginx 站点
homestead
使用sites
属性让你轻松配置nginx
域名。使用vagrant reload --provision
更新虚拟机中的nginx
配置。
配置好域名,记得在本地 hosts 文件添加解析记录。例如:192.168.10.10 homestead.test
。
启动和删除 box
# 修改 homestead.rb
# settings[“version”] ||= “>= 0”
# 找到 composer self-update 删除它
vagrant up
vagrant destroy --force
为每个项目安装 homestead
这样你就可以在其他项目目录内,通过 vagrant up 来进行该项目的工作。
composer require laravel/homestead --dev
// make 命令自动生成 Vagrantfile 和 Homestead.yaml
php vendor/bin/homestead make // linux & mac
vendor\\bin\\homestead make // windows
安装 mariadb
box: laravel/homestead
ip: "192.168.10.10"
memory: 2048
cpus: 4
provider: virtualbox
mariadb: true
安装 elasticsearch
需要指定版本,默认安装将创建一个名为 ‘homestead’ 的集群,确保你的虚拟机内存是elasticsearch
的两倍。
box: laravel/homestead
ip: "192.168.10.10"
memory: 4096
cpus: 4
provider: virtualbox
elasticsearch: 6
bash
命令别名
alias ..='cd ..' // 随后记得 vagrant reload --provision
日常用法
全局访问 homestead
mac 或 linux
function homestead() {
( cd ~/Homestead && vagrant $* )
}
windows
@echo off
set cwd=%cd%
set homesteadVagrant=C:\Homestead
cd /d %homesteadVagrant% && vagrant %*
cd /d %cwd%
set cwd=
set homesteadVagrant=
替换C:\Homestead
,再将脚本加入环境变量即可。
设置完成后可以在任何地方使用 homestead up
,homestead ssh
连接数据库
mysql 127.0.0.1 33060
postgreSQL 127.0.0.1 54320
在虚拟机里面连接数据库的端口还是默认值 3306
和 5432
。
账号密码:homestead / secret
添加多个网站
sites:
- map: homestead.test
to: /home/vagrant/code/Laravel/public
- map: another.test
to: /home/vagrant/code/another/public
192.168.10.10 homestead.test
192.168.10.10 another.test
添加 nginx 其他配置
sites:
- map: homestead.test
to: /home/vagrant/code/Laravel/public
params:
- key: FOO
value: BAR
配置时间任务
如果需要开启 artisan
命令schedule:run
一样的效果,配置如下
(schedule:run
命令的原理是每分钟都会去检查 App\Console\Kernel 类中是否有任务需要执行,有则执行。)
sites:
- map: homestead.test
to: /home/vagrant/code/Laravel/public
schedule: true
配置 Mailhog(.env)
Mailhog 用来捕获你的邮件,并检查它。你的邮件并不会真的发出去。
MAIL_DRIVER=smtp
MAIL_HOST=localhost
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
端口配置
默认:
SSH: 2222 → Forwards To 22
ngrok UI: 4040 → Forwards To 4040
HTTP: 8000 → Forwards To 80
HTTPS: 44300 → Forwards To 443
MySQL: 33060 → Forwards To 3306
PostgreSQL: 54320 → Forwards To 5432
Mailhog: 8025 → Forwards To 8025
自定义:
ports:
- send: 50000
to: 5000
- send: 7777
to: 777
protocol: udp
分享你的环境
vagrant share
但是如果Homestead.yaml
配置了多个站点,此命令不再支持。
解决方案,使用 homestead 内置命令:
vagrant ssh
share homestead.test
vagrant 天生就不安全,当你使用 share 命令,就会在互联网中暴露的你虚拟机。
切换 php 版本
只兼容 nginx
切换 web 服务器
apache 和 nginx 可以同时安装,但不可同时运行,原理应该是抢占 80 端口。
运行命令 flip
可以关闭其中一个,开启另一个。
配置网络接口
可以配置任意数量的接口:
networks:
- type: "private_network"
ip: "192.168.10.20"
想启用 桥接 接口,请配置 bridge
设置,并将网络类型更改为 public_network
:
networks:
- type: "public_network"
ip: "192.168.10.20"
bridge: "en1: Wi-Fi (AirPort)"
要启用 DHCP,只需从配置中删除 ip
选项:
networks:
- type: "public_network"
bridge: "en1: Wi-Fi (AirPort)"
更新 homestead
vagrant box update
git pull origin master
或者
vagrant box update
composer update # 确保 composer.json 包含 "laravel/homestead": "^7"
如果 windows 下符号链接不生效,添加以下代码块到
Vagrantfile
config.vm.provider "virtualbox" do |v|
v.customize ["setextradata", :id, "VBoxInternal2/SharedFoldersEnableSymlinksCreate/v-root", "1"]
end
六、部署
本节介绍了几个部署要点。
nginx 部署
server {
listen 80;
server_name example.com;
root /example.com/public;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
index index.html index.htm index.php;
charset utf-8;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php/php7.1-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
部署优化
composer install --optimize-autoloader
php artisan config:cache
php artisan route:cache
# 开启 OpCache
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
第三章:架构(一、生命周期。二、服务容器。三、服务提供者。四、facades。五、contracts。)
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
一、请求生命周期
本节主要概括了框架运行的生命周期。
- 所有请求必定首先通过
public/index.php
。 - 在上述这个文件中首先加载
composer
自动加载文件,然后从bootstrap/app.php
实例化一个服务容器(服务容器,顾名思义是一个容器,可以比作是一个药箱,药箱当然要放药了,药就是所谓的服务提供者。在启动时候当然不能把框架里的所有的药都加载进来,只能加载基础的药。所以这一步还加载了一些基础的服务提供者。)。 - 接下来,框架会根据请求类型传送请求至
app/Http/Kernel.php
或者app/Console/Kernel.php
。 app/Http/Kernel.php
扩展了Illuminate\Foundation\Http\Kernel
类,父类强制在处理请求前应该做哪些操作,操作内容都放到了bootstrappers
数组里面(配置错误处理、配置记录日志、检测应用环境、注册和启动服务提供者等)。子类在数组middleware
中规定了请求在被处理前必须经过的一些处理(读写session
、判断是否处于维护模式、验证 csrf 令牌等)。- 实例化
Http Kernel
,处理请求,返回响应内容。请求将作为参数传入handle
方法,返回值就是响应内容。
二、服务容器
本节主要讲了服务容器中的绑定,解析,解析事件(类似于在药瓶中放药,拿药,拿药时会发生什么)。
- 如果不依赖任何接口,则不需要将类绑定到容器中。
- 无需指示容器如何构建这些对象,因为它可以使用反射自动解析。
绑定
基础绑定
$this->app->bind('HelpSpot\API', function ($app) {
return new HelpSpot\API($app->make('HttpClient'));
});
// Note that we receive the container itself as an argument to the resolver. We can then use the container to resolve sub-dependencies of the object we are building.
// 然而我并不知道上面说的什么鸡脖子东西。
绑定单例
$this->app->singleton('HelpSpot\API', function ($app) {
return new HelpSpot\API($app->make('HttpClient'));
});
绑定实例
$api = new HelpSpot\API(new HttpClient);
$this->app->instance('HelpSpot\API', $api);
绑定实例时给定初始化数据
$this->app->when('App\Http\Controllers\UserController')
->needs('$variableName')
->give($value); // 利用上下文给绑定设置初始数据
绑定接口到实例
$this->app->bind(
'App\Contracts\EventPusher',
'App\Services\RedisEventPusher'
);
根据上下文绑定
$this->app->when(PhotoController::class)
->needs(Filesystem::class)
->give(function () {
return Storage::disk('local');
});
$this->app->when(VideoController::class)
->needs(Filesystem::class)
->give(function () {
return Storage::disk('s3');
});
给绑定设置标签
$this->app->bind('SpeedReport', function () {
//
});
$this->app->bind('MemoryReport', function () {
//
});
$this->app->tag(['SpeedReport', 'MemoryReport'], 'reports');
$this->app->bind('ReportAggregator', function ($app) {
return new ReportAggregator($app->tagged('reports'));
});
当Service
已经被解析,extend
方法可以用来修改解析出来的实例$service
$this->app->extend(Service::class, function($service) {
return new DecoratedService($service);
});
解析
基础解析
$api = $this->app->make('HelpSpot\API');
无法访问 $app
时,这样解析
$api = resolve('HelpSpot\API');
容器无法解决依赖时,通过关联数组注入依赖解析
$api = $this->app->makeWith('HelpSpot\API', ['id' => 1]); // 解析时候通过关联数组注入依赖
类型提示解析(最常用)
public function __construct(UserRepository $users)
# laravel 实现了 PSR-11 接口,所以就可以用该接口的类型提示解析
# get 方法通过 id 解析,比较局限,建议别用
use Psr\Container\ContainerInterface;
Route::get('/', function (ContainerInterface $container) {
$service = $container->get('Service');
//
});
容器事件
容器解析任何对象时调用
$this->app->resolving(function ($object, $app) {
});
容器解析HelpSpot\API
时调用
$this->app->resolving(HelpSpot\API::class, function ($api, $app) {
});
三、服务提供者
加载服务提供者是框架启动的关键步骤之一,他们负责启动不同的组件(数据库、队列、验证、路由等),服务提供者被配置在 config/app.php
中的providers
数组。首先,他们的register
方法会被调用,全部调用结束,才会依次调用他们的boot
方法。当所有服务提供者都注册好了,Request
将会被路由器分发到路由或控制器,同时运行路由指定的中间件。
制作一个服务提供者
php artisan make:provider RiakServiceProvider
- 服务提供者主要由两个方法:
register
和boot
。register
只负责绑定一些东西到容器。boot
可以使用类型提示解析等来完成任意你想做的事情,这些都归功于容器调用所有服务提供者的register
方法之后才去调用boot
方法。 - 在
config/app.php
的providers
数组中注册服务提供者。
制作一个延迟服务提供者
如果只是绑定服务到服务容器,可以选择将该服务提供者设置为延迟
protected $defer = true;
public function provides()
{
return [Connection::class];
}
四、Facades
原理
其实facades
就是各种别名。当你使用某个facade
的静态方法时,会触发它的父类的__callStatic
方法,该方法会找到注册在容器中的facade
原类名,最终调用原类名中的对应方法。
不要在一个类中,用太多的
facades
。过于臃肿的情况下应该将大类分解成几个小类。
优点
方便测试(辅助函数和 facades 没什么区别,测试方法也是一样的)。
Route::get('/cache', function () {
return Cache::get('key'); // === return cache('key');
});
public function testBasicExample()
{
Cache::shouldReceive('get')
->with('key')
->andReturn('value');
$this->visit('/cache')
->see('value');
}
实时的 facades
原生用法 vs 实时用法
use App\Contracts\Publisher;
...
# 注入 Publisher $publisher
$publisher->publish($this);
use Facades\App\Contracts\Publisher;
...
Publisher::publish($this);
测试实时的 facades
use Facades\App\Contracts\Publisher;
Publisher::shouldReceive('publish')->once()->with($podcast);
facades 列表
五、Contracts
Facades
和Contracts
没有什么值得注意的区别,但是当你开发第三方包的时候,最好使用Contracts
,这样有利于你编写测试,否则如果使用Facades
,因为是第三方包,将不能访问facade
测试函数。
使用方法
在构造函数中类型提示注入就行了。
Contracts 列表
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
第四章:基础(一、路由。二、中间件。三、CSRF 保护。四、控制器。五、请求。六、响应。七、视图。八、Url 生成。九、Session 。十、验证。十一、错误与日志)
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
一、路由
web/api.php
中定义的路由会自动添加api
前缀,如需修改该前缀,可以在RouteServiceProvider
修改。
在 web.php
路由里的POST, PUT, DELETE
方法,在提交表单时候必须加上CSRF
参数。
两个 api Router 和 Route
resource
* GET /test
index() // 展示列表
* GET /test/create
create() // 展示用来创建的表单
* POST /test
store() // 增加资源
* GET /test/{id}
show($id) // 展示一个资源
* GET /test/{id}/edit
edit($id) // 展示编辑表单
* PUT /test/{id}
update($id) // 更新特定资源
* DELETE /test/{id}
destroy($id) // 移除资源
基本路由
Route::get($uri, $callback);
Route::post($uri, $callback);
Route::put($uri, $callback); // 全体更新
Route::patch($uri, $callback); // 局部更新
Route::delete($uri, $callback);
Route::options($uri, $callback); // 允许客户端检查性能
Route::any($uri, $callback); // 任意 method
Route::match(['get', 'post'], '/', function () {
//
});
# 重定向路由
Route::redirect('/here', '/there', 301);
# 只需要返回一个视图
Route::view('/welcome', 'welcome', ['name' => 'Taylor']);
路由参数
Route::get('posts/{post}/comments/{comment}', function ($postId, $commentId) {
//
});
Route::get('user/{name?}', function ($name = 'John') {
// 一定要给可选参数设置默认值
return $name;
});
正则表达式约束
Route::get('user/{id}', function ($id) {
//
})->where('id', '[0-9]+');
Route::get('user/{id}/{name}', function ($id, $name) {
//
})->where(['id' => '[0-9]+', 'name' => '[a-z]+']);
全局约束
// RouteServiceProvider
public function boot()
{
Route::pattern('id', '[0-9]+');
parent::boot();
}
命名路由
Route::get('user/profile', function () {
//
})->name('profile');
// 使用
$url = route('profile');
// 生成重定向...
return redirect()->route('profile');
// 在中间件中检查当前路由
public function handle($request, Closure $next)
{
if ($request->route()->named('profile')) {
//
}
return $next($request);
}
添加中间件
Route::middleware(['first', 'second'])->group(function () {
Route::get('/', function () {
// 使用 `first` 和 `second` 中间件
});
Route::get('user/profile', function () {
// 使用 `first` 和 `second` 中间件
});
});
命名空间
Route::namespace('Admin')->group(function () {
// 在 "App\Http\Controllers\Admin" 命名空间下的控制器
});
子域名路由
Route::domain('{account}.myapp.com')->group(function () {
Route::get('user/{id}', function ($account, $id) {
//
});
});
路由前缀
Route::prefix('admin')->group(function () {
Route::get('users', function () {
// 匹配包含 "/admin/users" 的 URL
});
});
路由命名前缀
Route::name('admin.')->group(function () {
Route::get('users', function () {
// Route assigned name "admin.users"...
});
});
表单伪造
<input type="hidden" name="_method" value="PUT">
// 或者 {
{ method_field('PUT') }}
获取当前路由信息
// Route::get('/', 'TestController@test')->name("mytest");
$route = Route::current(); // object(Illuminate\Routing\Route)
$name = Route::currentRouteName(); // mytest
$action = Route::currentRouteAction(); // 控制器中:App\Http\Controllers\TestController@test 路由中:null
隐式绑定
Route::get('api/users/{user}', function (App\User $user) {
return $user->email; // 传人的 id
});
# 自定义键名 在模型中修改
# App/User.php
public function getRouteKeyName()
{
return 'slug';
}
显式绑定
# RouteServiceProvider
public function boot()
{
parent::boot();
Route::model('user', App\User::class);
}
Route::get('profile/{
user}', function ($user) {
//
});
自定义解析逻辑
public function boot()
{
parent::boot();
Route::bind('user', function ($value) {
return App\User::where('name', $value)->first() ?? abort(404);
});
}
二、中间件
php artisan make:middleware TestMiddleware
# 中间件操作发生请求被处理之前
public function handle($request, Closure $next)
{
// Perform action
return $next($request);
}
# 中间件操作发生请求被处理之后
public function handle($request, Closure $next)
{
$response = $next($request);
// Perform action
return $response;
}
注册全局中间件,就将完整类名写在app/Http/Kernel.php
文件中的$middleware
数组中。如果是非全局,部分的,可以放在文件的其他数组中。也可以不注册,在需要使用时,引入直接使用。
使用中间件的两种方式
# 常用方式
Route::get('admin/profile', function () {
})->middleware('auth');
# 不用注册的方式
use App\Http\Middleware\CheckAge;
Route::get('admin/profile', function () {
})->middleware(CheckAge::class);
如果想应用中间件组,请注册一个关联数组到app/Http/Kernel.php
的$middlewareGroups
中。这样就可以使用时填写该数组的键值就行了。默认情况下,RouteServiceProvider
已将中间件组web
应用在你的web.php
的路由中。
给 middleware 传参
// role 中间件
public function handle($request, Closure $next, $role)
{
if (! $request->user()->hasRole($role)) {
// Redirect...
}
return $next($request);
}
Route::put('post/{id}', function ($id) {
//
})->middleware('role:editor');
terminate
方法调用于将响应发送到浏览器之后。
# terminate 会从服务容器解析一个新的中间件实例。
# 如果你希望 handle 和 terminate 是同一实例,请将这个中间件单例绑定到服务容器中。
public function handle($request, Closure $next)
{
return $next($request);
}
public function terminate($request, $response)
{
// Store the session data...
}
三、CSRF 保护
VerifyCsrfToken
中间件存在于web
中间件组中,它实现了 CSRF 保护。而该中间件组被默认应用在web.php
文件中的所有路由。
默认情况下,resources/assets/js/bootstrap.js
文件会使用Axios HTTP
库注册csrf-token
meta 标签的值。如果不使用这个库,需要自己去设置。
指定 uri 不去应用 csrf 保护
1. 不将路由写入route/web.php
。
2. 在VerifyCsrfToken
中间件的排除数组中添加你的 uri
protected $except = [
'stripe/*',
'http://example.com/foo/bar',
'http://example.com/foo/*',
];
除了可以在验证 post 时作为表单参数传递 csrf ,还可以设置 meta 标签来进行 csrf 保护(如 ajax )。
设置:<meta name="csrf-token" content="{
{ csrf_token() }}">
ajax 获取:'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
cookie中,XSRF-TOKEN
的值可以直接设为X-CSRF-TOKEN
请求头的值。Angular
、Axios
等会自动执行上述操作。
四、控制器
定义控制器可以不去继承Controllers
,这样你将不能使用middleware
、validate
、dispatch
等方法。
如果你的控制器只有一个方法,可以这么玩儿:
# Route::get('foo', 'Photos\AdminController@method');
public function __invoke($id)
{
return view('user.profile', ['user' => User::findOrFail($id)]);
}
控制器的构造函数中的中间件的玩法
$this->middleware('auth'); $this->middleware('log')->only('index'); $this->middleware('subscribed')->except('store'); $this->middleware(function ($request, $next) { return $next($request); });
Resource 控制器
php artisan make:controller PhotoController --resource php artisan make:controller PhotoController --resource --model=Photo Route::resource('photos', 'PhotoController'); Route::apiResource('photo', 'PhotoController'); // 没有 create 和 edit Route::resources([ 'photos' => 'PhotoController', 'posts' => 'PostController' ]); Route::resource('photo', 'PhotoController', ['only' => [ 'index', 'show' ]]); Route::resource('photo', 'PhotoController', ['except' => [ 'create', 'store', 'update', 'destroy' ]]); Route::resource('photo', 'PhotoController', ['names' => [ 'create' => 'photo.build' ]]); # 模拟 http 请求方法 {
{ method_field('PUT') }}
Verb | URI | Action | Route Name |
---|---|---|---|
GET | /photos |
index | photos.index |
GET | /photos/create |
create | photos.create |
POST | /photos |
store | photos.store |
GET | /photos/{photo} |
show | photos.show |
GET | /photos/{photo}/edit |
edit | photos.edit |
PUT/PATCH | /photos/{photo} |
update | photos.update |
DELETE | /photos/{photo} |
destroy | photos.destroy |
命名 resource 路由的参数
默认情况下,Route::resource
会根据资源名称的「单数」形式创建资源路由的路由参数。你可以在选项数组中传入 parameters
参数来轻松地覆盖每个资源。parameters
数组应该是资源名称和参数名称的关联数组:
Route::resource('user', 'PhotoController', ['parameters' => [
'photo' => 'photo_in_phone'
]]);
# /user/{photo_in_phone}
重命名动词名 create、edit
# AppServiceProvider
Route::resourceVerbs([
'create' => 'crear',
'edit' => 'editar',
]);
- 如果想加入其他控制器方法,尽量写在
resource
控制器路由之前,否则可能会被resource
控制器的路由覆盖。- 如果你需要典型的
resource
操作之外的方法,可以考虑将你的控制器分成两个更小的控制器。
参数必须在依赖注入之后传入
# Route::put('user/{id}', 'UserController@update');
public function update(Request $request, $id)
{
//
}
五、请求
请求将经过TrimStrings
、ConvertEmptyStringsToNull
等中间件,这样可以不用担心标准化的问题。
$request->path() $request->is('admin/*') $request->url() $request->fullUrl() $request->isMethod('post') $request->all() $request->input('name') $request->input('name', 'Sally') // 第二个参数为默认值 $request->input('products.0.name') $request->input('products.*.name') $request->query('name') $request->query('name', 'Helen') $request->query() // 返回所有 query string 的关联数组 $request->name // laravel 首先查找请求数据,在查找路由参数 $request->input('user.name') // 获取 json,请求 contentType: "application/json" $request->only(['username', 'password']) // 返回请求关联数组,但不会返回不存在请求数据 $input = $request->only('username', 'password'); // 同上 $input = $request->except(['credit_card']); $input = $request->except('credit_card'); $request->has('name') $request->has(['name', 'email']) // 同时存在 $request->filled('name') // 存在并且为非空 $request->flash() // 将当前输入存进 session 中,以便下次请求可以使用它们 $request->flashOnly(['username', 'email']) $request->flashExcept('password') redirect('form')->withInput() // 等同于:$request->flash(); redirect('form'); redirect('form')->withInput( $request->except('password') ); $request->old('username') // 取出 flash() 内容,并从 session 中清除 <input type="text" name="username" value="{
{ old('username') }}"> // 不存在返回 null $request->cookie('name') // === Cookie::get('name') response('Hello World')->cookie( 'name', 'value', $minutes ); response('Hello World')->cookie( 'name', 'value', $minutes, $path, $domain, $secure, $httpOnly ); Cookie::queue(Cookie::make('name', 'value', $minutes)); Cookie::queue('name', 'value', $minutes); $cookie = cookie('name', 'value', $minutes); response('Hello World')->cookie($cookie); $request->file('photo') $request->photo $request->hasFile('photo') $request->file('photo')->isValid() $request->photo->path() $request->photo->extension() $path = $request->photo->store('images'); $path = $request->photo->store('images', 's3'); $path = $request->photo->storeAs('images', 'filename.jpg'); $path = $request->photo->storeAs('images', 'filename.jpg', 's3');
六、响应
laravel 会自动将你的 return 数据封装成响应。如果你返回数组,会自动封装成 json。如果返回 Eloquent 集合
,也会自动封装成 json。
自定义
return response('Hello World', 200) ->header('Content-Type', 'text/plain'); // 或者 return response($content) ->withHeaders([ 'Content-Type' => $type, 'X-Header-One' => 'Header Value', 'X-Header-Two' => 'Header Value', ]) return response($content) ->header('Content-Type', $type) ->cookie('name', 'value', $minutes); # ->cookie($name, $value, $minutes, $path, $domain, $secure, $httpOnly) # Cookie::queue(Cookie::make('name', 'value', $minutes)); # Cookie::queue('name', 'value', $minutes); # 如果不想某个 cookie 在客户端中加密, # 请在 App\Http\Middleware\EncryptCookies 的 except 数组中进行配置。
重定向
return redirect('home/dashboard');
return back()->withInput(); // 请确保该路由在`web.php`中
return redirect()->route('login');
return redirect()->route('profile', ['id' => 1]);
return redirect()->route('profile', [$user]);
// 第二个参数也可以是 $user,路由:profile/{id}。该写法会自动提取 $user 中的 id
# 如果想定制自动提取的字段
public function getRouteKey()
{
return $this->slug;
}
return redirect()->action(
'UserController@profile', ['id' => 1]
);
return redirect()->away('https://www.google.com');
return redirect('dashboard')->with('status', 'Profile updated!');
// 设置 cookie,获取 {
{ session('status') }}
生成其他类型的响应
return response()
->view('hello', $data, 200)
->header('Content-Type', $type);
return response()->json([
'name' => 'Abigail',
'state' => 'CA'
]); // json 会自动设置响应头 Content-Type => application/json
实现 jsonp 响应
return response()
->json(['name' => 'Abigail', 'state' => 'CA'])
->withCallback($request->input('callback'));
实现下载
return response()->download($pathToFile);
return response()->download($pathToFile, $name, $headers);
return response()->download($pathToFile)->deleteFileAfterSend(true);
管理文件下载的扩展包 Symfony HttpFoundation,要求下载文件名必须是 ASCII 编码。
展示图片、pdf 等
return response()->file($pathToFile);
return response()->file($pathToFile, $headers);
定制通用响应
// 定制
Response::macro('caps', function ($value) {
return Response::make(strtoupper($value));
});
// 使用
return response()->caps('foo');
七、视图
return view('greeting', ['name' => 'James']);
if (View::exists('emails.customer'))
return view()->first(['custom.admin', 'admin'], $data);
return View::first(['custom.admin', 'admin'], $data); // 同上
return view('greeting')->with('name', 'Victoria'); // === ['name' => 'Victoria']
public function boot()
{
View::share('key', 'value');
}
视图 composers
// service provider
public function boot()
{
View::composer(
'profile', 'App\Http\ViewComposers\ProfileComposer'
); // ProfileComposer@compose 在 profile 视图生成前调用
View::composer('dashboard', function ($view) {
//
});
}
class ProfileComposer
{
protected $users;
public function __construct(UserRepository $users)
{
$this->users = $users;
}
public function compose(View $view)
{
$view->with('count', $this->users->count());
}
}
# 多个视图
View::composer(
['profile', 'dashboard'],
'App\Http\ViewComposers\MyViewComposer'
);
# 所有
View::composer('*', function ($view) {
//
});
视图 creator
View::creator('profile', 'App\Http\ViewCreators\ProfileCreator');
视图 creator 和视图合成器非常相似。唯一不同之处在于:视图构造器在视图实例化之后立即执行,而视图合成器在视图即将渲染时执行。creator 在 composer 之前。
八、url 生成
echo url("/posts/{$post->id}"); // http://example.com/posts/1 echo url()->current(); // === URL::current(); echo url()->full(); // === URL::full(); echo url()->previous(); // === URL::previous(); echo route('post.show', ['post' => 1]); // http://example.com/post/1 echo route('post.show', ['post' => $post]); // $post 是一个Eloquent model,该写法自动提取出主键 $url = action('HomeController@index'); $url = action('UserController@profile', ['id' => 1]); URL::defaults(['locale' => $request->user()->locale]); // 设置默认值 Route::get('/{locale}/posts', function () { ... })->name('post.index');
九、session
驱动设置为 array,是用在测试的时候保证不会持久存储 session。
使用 database 作为驱动
Schema::create('sessions', function ($table) {
$table->string('id')->unique();
$table->unsignedInteger('user_id')->nullable();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->text('payload');
$table->integer('last_activity');
});
php artisan session:table
php artisan migrate
session 操作
$request->session()->get('key'); $request->session()->get('key', 'default'); $request->session()->get('key', function () { return 'default'; }); session('key'); session('key', 'default'); session(['key' => 'value']); $request->session()->all(); if ($request->session()->has('users')) // 存在且不为 null if ($request->session()->exists('users')) // 可以是 null $request->session()->put('key', 'value'); session(['key' => 'value']); $request->session()->push('user.teams', 'developers'); $value = $request->session()->pull('key', 'default'); // 取出并删除 $request->session()->flash('status', 'Task was successful!'); // 短暂存储 session 一次 $request->session()->reflash(); // 保持 session 再存储一次 $request->session()->keep(['username', 'email']); // 保持特定 session 存储一次 $request->session()->forget('key'); $request->session()->flush(); $request->session()->regenerate(); // 手动重新生成 session id,LoginController 内置自动重新生成 session id
自定义 session 驱动
# SessionHandlerInterface 必须实现此接口
<?php
namespace App\Extensions; // 随意放置于哪个目录
class MongoSessionHandler implements \SessionHandlerInterface
{
public function open($savePath, $sessionName) {
}
public function close() {
}
public function read(