lumen

时间:2023-03-09 15:50:36
lumen

HTTP路由

  • 基本路由

  • 路由参数

    1. 必填参数
    2. 可选参数
    3. 正则表达式约束
  • 命名路由

  • 路由组

    1. 中间件
    2. 命令空间
    3. 路由前缀

基本路由

你可以在 route/web.php 文件中定义应用程序的全部路由。最基本的Lumen路由仅接受URL和一个Closure:

$router->get('foo', function(){
return 'Hello, World';
}); $router->post('foo', function(){
//
});

可供使用的路由方法

我们可以注册路由来响应任何方法的HTTP请求:

$router->get($uri, $callback);
$router->post($uri, $callback);
$router->put($uri, $callback);
$router->patch($uri, $callback);
$router->delete($uri, $callback);
$router->options($uri, $callback);

路由参数

必填参数

当然,有时需要在路由中捕获一些URL片段。例如,从URL中捕获用户的ID,可以通过通过定义路由参数来执行此操作:

$router->get('user/{id}', function ($id) {
return "User:" . $id;
});

也可以根据需要在路由中定义多个参数:

$router->get('posts/{postId}/comments/{commentId}', function ($postId, $commentId) {
//
});

路由的参数都会被放在[大括号]内。当运行路由时,参数会传递到Closure里面。

注意:路由参数不能包含-字符。请用下划线_代替。

可选参数

你可以通过将部分路由URI包含在[...]中来定义可选的路由参数。那么像/foo[bar]将会匹配到/foo和/foobar。可选参数仅支持放在URI的末尾。换句话说,你不能在路由定义的中间位置放置可选参数:

$router->get('user'[/{name}]', function ($name = null) {
return $name;
});

正则表达式约束

你可以通过在路由定义中使用正则表达式来约束路由参数的格式:

$router->get('user2/{name:[a-zA-Z]}', function () {
//
});

命名路由

命名路由可以方便的为特定路由生成URL或者进行重定向。你可以使用as数组键指定名称到路由上:

$router->get('profile', ['as' => 'profile', function() {
//
}]);

你还可以指定控制器行为的路由名称:

$router->get('profile', [
'as' => 'profile',
'uses' => 'UserController@showProfile',
]);

生成指定路由的URL

为路由制定了名称后,就可以使用全局辅导函数route来生成链接或者重定向到该路由:

//Generating URLs...

$url = route('profile');

//Generating Redirects...

return redirect()->route('profile');

如果是有定义参数的命名路由,可以把参数作为route函数的第二个参数传入,指定的参数将会自动插入到URL中对应的位置:

$router->get('user/{id}/profile', ['as' => 'profile', function ($id) {
return $id;
}]); $url = route('profile', ['id' => 1]);

路由组

路由群组允许你共用路由属性,例如:中间件、命名空间,你可以利用路由组统一为多个路由设置共同属性,而不需要在每个路由都设置一次。共用属性被指定为数组格式,当做$router->group方法的第一个参数。

为了了解更多路由群组的相关内容,我们可以通过几个常用样例来熟悉这些特性。

中间件

要给路由组中所有的路由分配中间件,你可以在group属性数组中使用middleware字段。中间件会依照它们在数组中列出的顺序来运行:

$router->group(['middleware' => 'auth'], function () use ($router) {
$router->get('/', function () {
//使用Auth中间件
}); $router->get('user/profile', function () {
//使用Auth 中间件
});
});

命名空间

另一个常见的例子是,指定相同的PHP命名空间给控制器群组。可以使用namespace参数来指定群组内所有控制器的命名空间:

$router->group(['namespace' => 'Admin'], function () use ($router) {
//使用"App\Http\Controllers\Admin"命名空间
$router->group(['namespace' => 'User', function () use ($router) {
//使用"App\Http\Controllers\Admin\User" 命名空间...
}]);
});

路由前缀

通过路由群组数组属性中的prefix,在路由群组内为每个路由指定的URI加上前缀。例如,你可能想要在路由群组中将所有的路由URI加上前缀admin:

$router->group(['prefix,' => 'admin'], function () use ($roter) {
$router->get('users', function () {
//匹配The "/admin/users"URL
});
});

你也可以使用prefix参数去指定路由群组*用的参数:

$router->group(['prefix' => 'accounts/{accountId}'], function () use ($router) {

$router->get('detail', function ($accountId) {

//匹配The "/accounts/{accountId}/detail" URL

});

});

HTTP中间件

  • 简介

  • 定义中间件

  • 注册中间件

    • 全局中间件
    • 为路由指定中间件
  • 中间件参数

  • Terminal中间件

简介

HTTP中间件提供了一个方便的机制来过滤进入应用程序的HTTP请求。例如,lumen内置了一个中间件来验证用户的身份认证。如果用户未通过省份证,中间件将会把用户导向登录页面,反之,当用户通过了身份证,中间件将会通过此请求并接着往下执行。

当然,除了身份证之外,中间件也可以被用来运行各式各样的任何,如:CORS中间件负责替换所有即将离开程序的响应加入适当的标头;而日志中间件则可以记录所有出入应用从程序的请求。

所有的中间件都放在app/Http/Middleware目录内。

定义中间件

你可以通过赋值lumen内置的示例文件ExampleMiddleware来创建一个中间件。在这个中间件中,我们只允许参数age大于200的请求才能访问该路由。否则,我们将此用户重定向到首页"home"这个URI上。

正如你所见,假如给定的age参数小于或者等于200,这个中间件将返回一个HTTP重定向到客户端;否则,请求将进一步传递到应用中。要让请求继续传递到应用程序中(即允许“通过”中间件验证的),只需要使用$request作为参数去调用回调函数$next。

最好将中间件想象为一系列HTTP请求必须经过才能进入你的应用的层。每一个都会检查请求

(是否符合某些条件),(如果不符合)甚至可以(在请求访问你的应用之前)完全拒绝掉。

前置/后置中间件

中间件是在请求之前或者之后运行取决于中间件本身。例如,接下来的这个中间件将在应用处理请求before执行其任务:

middleware()方法中列出这个中间件:

$app->middleware([
App\Http\Middleware\OldMiddleware::class
]);

### 为路由分配中间件 ###

如果你想将中间件分配给特定的路由,首先需要在bootstrap/app.php文件中调用$app->routeMiddleware()方法时为中间件分配一个简短的键:

$app->routeMiddleware([
'auth' => App\Http\Middleware\Authenticate::class,
]);

一旦在HTTP内核中定义好了中间件,就可以在路由选项内使用middleware键:

$router->get('admin/profile', ['middleware' => 'auth', function () {
//
}]);

可以使用数组为路由指定多个中间件:

$router->get('/', ['middleware' => ['first', 'second'], function () {
//
}]);

# 中间件参数 #

中间件也可以接受自定义传参,例如,要在运行特定操作前检查已验证用户是否具备该操作的“角色”,可以创建RoleMiddleware来接受角色名称作为额外的传参。

附加的中间件参数将会在$next参数之后被传入中间件:

user()->hasRole($role)) {
//重定向...
}

return $next($request);
}
}

在路由中可以使用冒号:来区隔中间件名称与指派参数,多个参数可以使用逗号作为分割:

$router->put("post/{id}", ['middleware' => 'role:editor', function($id) {
//
}]);

### Terminable 中间件 ###

有时中间件可能需要在HTTP响应发送到浏览器哦之后处理一些工作。例如,“session”中间件会在响应发送到浏览器之后将会话数据写入存储器中。想要做到这一点,你需要定义一个名为“terminable”的中间并添加一个terminal方法:

get("user/{id}", "UserController@show");

现在,当请求匹配到这个特定的URL时,UserController类中的show方法就会执行。当然,路由的参数也同样传递给了这个方法。

### 控制器和命名空间 ###

有一点非常重要,那就是我们要注意在定义控制器路由时,不需要指定完整的控制器命名空间。我们只需定义“根”命名空间App\Http\Controllers之后的类名部分。默认情况下,bootstrap/app.php文件在加载routes.php时已经把所有路由都配置到了根控制器命名空间。

如果你选择在App\Htpp\Controllers目录内层使用PHP命名空间嵌套或者组织控制器,只要使用相对于App\Http\Controllers根命名空间的特定名称即可。因此,如果你的控制器类全名是App\Http\Controllers\Photos\AdminController,那么你应该注册一个路由,如下所示:

$router->get('foo', 'Photos\AdminController@method');

### 命名控制器路由 ###

像闭包那样,你可以给控制器路由指定一个名称:

$router->get('foo', ['uses' => 'FooController@method', 'as' => 'name']);

你也可以使用route辅助函数,来生成指向控制器路由的URL:

$url = route('name');

### 控制器中间件 ###

中间件可通过如下方式分配到路由中:

$router->get('profile', [
'middleware' => 'auth',
'uses' => 'UserController@showProfile'
]);

然而,更方便的方式是在控制器的构造方法里面使用middleware方法指定中间件。你甚至可以限制中间件只应用于该控制器类的某些方法:

class UserController extends Controller
{
/**
*实例化一个新的UserController实例。
*@return void
*/
public function __construct()
{
$this->middleware('auth');

$this->middleware('log', [
'only' => ['fooAction', 'barAction']
]);

$this->middleware('subscribed', ['except' => [
'fooAction',
'barAction',
]]);
}
}

## 依赖注入与控制器 ##

#### 构造器注入 ####

lumen使用[服务容器]来解析所有的控制器的依赖注入。因此,你可以在控制器的构造函数中使用类型提示需要的任何依赖。这些依赖会自动的解析并注入到控制器实例中:

namespace App\Http\Controllers;

use App\Repositories\UserRepository;

class UserController extends Controller
{
/**
*新建一个控制器实例
*
*@param UserRepository $users
*@return void
*/
public function __contruct(UserRepository $users)
{
$this->users = $users;
}
}

方法注入

除了构造器注入以外,你也可以在你的控制器方法中使用类型提示依赖。例如,在某个方法中添加Illuminate\Http\Request实例的类型提示:

input('name');
}
}

如果你想在控制器里获取路由参数,只需要在路由之后列出参数即可。例如,你的路由这样来定义:
$router->put('user/{id}', 'UserController@update');

你可以像下面的例子一样定义你的控制器,用类型提示注入Illuminate\Http\Request类和你的路由参数id:

input('name');
//
}
}

如果你的控制器方法也期望从路由参数中获取数据,只需要将路由参数放在其他依赖后面,比如你的路由是这样定义的:

$router->put('user/{id}', 'UserController@update');

像下面这样定义你的控制器方法,就可以使用Illuminate\Http\Request类型提示,同时获取到路由参数id:

path();

is方法会返回请求的URI是否与指定规则匹配,你可以使用*符号作为通配符:

if($request->is('admin/*')) {
//
}

如果要获取完整的URL而不是URI,可以使用url或者fullURL方法:

//不包含请求参数
$url = $request->url();

//包含请求参数
$url = $request->fullUrl();

# 获取请求的方法 #

method方法将会请求的HTTP动作,你可以使用isMethod方法校验HTTP动作是否与指定字符串匹配:

$method = $request->method();

if ($request->isMethod('post')) {
//
}

# PSR-7请求 #

PSR-7标准规定了HTTP消息接口包含了请求及响应,如果你想获得PSR-7的请求实例,就需要先
安装几个库,laravel使用Symfony的HTTP消息桥组件,将原laravel的氢气及响应转换至PSR-7所支持的实现:

composer require symfony/psr-http-message-bridge

composer require zendframework/zend-diactors

安装完这些库后,你就可以在路由或者控制器中,简单的对请求类型使用类型提示来获取PSR-7请求:

use Psr\Http\Message\ServerRequestInterface;

$router->get('/', function (ServerReuestInterface $request) {
//
});

如果你从路由或者控制器反悔了一个PSR-7的响应实例,那么它会被框架自动转换为laravel的响应实例并显示。

# 获取输入数据 #

## 获取指定输入值 ##

你可以通过Illuminate\Http\Request实例,使用几个简单的方法来获取所有的用户输入数据,而不需要担心请求的HTTP动作,因为它们的获取方式是相同的:

$name = $request->input('name');

你可以在input方法的第二个参数中传入一个默认值,当请求参数不存在时,就会返回默认值:

$name = $request->input('name', 'Sally');

当数据是以数组形式输入时,你可以使用“点”符号来获取数组:

$name = $request->input('products.0.name');

$name = $request->input('products.*.name');

# 确认输入值是否存在 #

你可以通过has方法判断输入值是否存在,输入值存在时has方法将会返回true:

if($request->has('name')) {
//
}

当给定一个数组时,has方法将确认是否所有指定值都存在:

if ($request->has(['name', 'email'])) {
//
}

如果你想确定请求中是否存在值并且不为空,可以使用filled方法:

if($request->filled('name')) {
//
}

# 获取所有输入数据 #

你可以使用all方法以数组形式获取所有的输入数据:

$input = $request->all();

# 获取部分输入数据 #

如果你想获取数据的子集,你可以是only和except方法,这两个方法都接受单个数组或者动态列表作为参数:

$input = $request->only(['username', 'password']);

$input = $request->only('username', 'password');

$input = $request->except(['credit_card']);

$input = $request->except('credit_card');

# 文件上传 #

### 获取上传文件 ###

你可以使用Illuminate\Http\Request实例中的发file方法获取上传的文件,file方法返回的对象是Symfony\Component\HttpFoundation\File\UploadFile类的实例,这个类继承了PHP的SplFileInfo类,并且提供了多种与文件交互的方法:

$file = $request->file('photo');

你可以使用hasFile方法确认上传的文件是否存在:

if ($request->hasFile('photo')) {
//
}

验证上传是否成功

除了检查文件是否存在之外,你还可以通过isValid方法验证上传是否存在问题:

if ($request->file('photo')->isValid()) {
//
}

## 移动上传文件 ##

要将上传的文件移动到新的位置,你应该使用move方法,这个方法会将文件从临时位置(由PHP配置决定)移动到你指定永久存储位置:

$request->file('photo')->move($destinationPath);

$request->file('photo')->move($destinationPath, $fileName);

# 其他上传文件方法 #

UploadFile实例还有很多其他可用的方法,可以到该类的API文档了解这些方法的详细信息。

# HTTP响应 #

## 基本响应 ##

当然,所有的路由及控制器必须返回某个类型的响应,并发送回用户的浏览器。laravel提供了集中不同的方法来返回响应。最基本的响应就是从路由或者控制器简单的返回一个字符串:

$router->get('/', function () {
return 'Hello World';
});

指定的字符串会被框架自动转为HTTP响应。

### 响应对象 ###

但是,对于大多数路由和控制器行为操作,你将返回完整的Illuminate\Http\Response实例。返回完整的Response实例允许你自定义响应的HTTP状态码和标题。一个Response实例继承自Symfony\Componet\HttpFoundation\Response类,并且提供了多种构建HTTP响应的方法:

use Illuminate\Http\Response;

$router->get('home', function () {
return (new Response($content, $status))
->header('Content-Type', $value);
});

为了方便起见,你可以使用response辅助函数:

$router->get('home', function () {
return response($content, $status)
->header('Content-Type', $value);
});

注意:有关Response方法的完整列表可以参考API文档一级Symfony api文档。

## 附加标头至响应 ##

大部分的响应方法是可以链式调用的,折让你可以顺畅的创建响应。举例来说,你可以在响应发送给用户之前,使用header方法增加一系列的标头至响应:

return response($content)
->header('Content-Type', $type)
->header('X-header-One', 'header Value')
->header('X-Header-Two', 'header Value');

或者你可以使用withHeaders方法来设置数组标头:
return response($content)
->withHeaders([
'Content-Type' => $type,
'X-header-One' => 'Header Value',
'X-header-Two' => 'Header Value',
]);

## 其他响应类型 ##

使用辅助函数response可以轻松的生成其他类型的响应实例、当你调用辅助函数response并且不带任何参数时,将会返回Laravel\Lumen\Http\ResponseFactory contract的实现。此Contract提供了一些有用的方法来生成响应。

JSON响应

json方法会自动将标头的Content-Type设置为application/json。并且通过PHP的接送_encode函数将制定的数组转换为json:

return response()->json(['name' => 'Abigail', 'state' => 'CA']);

你可以选择提供一个状态码和一个额外的标题数组:

return response()->json(['error' => 'Unauthorized'], 401, ['X-Header-One' => 'Header Value']);

如果你想创建一个JSONP响应,则可以使用json方法并加上setCallback方法:

return response()
->json(['name' => 'Abigail', 'state' => 'CA'])
->setCallback($request->input('callback'));

文件下载

download方法可以用于生成强制让用户的浏览器下载指定路劲文件的响应。download方法接受文件名称作为方法的第二个参数,此名称为用户下载文件时看见的文件名称。最后,你可以传递一个标头的数组作为第三个参数传入该方法:

return reponse()->download($pathToFile);

return response()->download($pathToFile, $name, $headers);

注意:管理文件下载的扩展包Symfony HTTPFoundation,要求下载文件必须是ASCII文件名。

## 重定向 ##

重定向响应是类illuminate\Http\RedirectResponse的实例,并且包含用户要重定向至另一个URL所需的正确标头。有几种方法可以生成RedirectResponse的实例。最简单的方法就是通过redirect辅助函数:

$router->get('dashboard', function () {
return redirect('home/dashboard');
});

### 重定向至命名路由 ###

当你调用redirect辅助函数并且不带任何参数时,将会返回laravel\Lumen\Http\Redirecotr的实例,你可以对该Redirector的实例调用任何方法。举个例子,要生成一个RedirectResponse到一个命名路由,你可以使用route方法:

return redirect()->route('login');

如果你的路由有参数,则可以将参数放进route方法的第二个参数,如下:

//For a route with follwing URI:profile/{id}
return redirect()->route('profile', ['id' => 1]);

如果你要重定向至路由并且路由的参数为Eloquent模型的[ID],则可以直接将模型传入,ID将会自动被提取:

return redirect()->route('profile', [$user]);

## 用户认证 ##

#### 简介 ####

lumen虽然与laravel使用了相同的底层类库实现,但是因lumen面向的是无状态API的开发,不支持session,所以默认的配置不同。lumen必须使用无状态的机制来实现认证,如api令牌(Token)。

#### 开始 ####

#### 认证服务提供者 ####

注意:在使用lumen的认证功能前,请取消bootstrap/app.php文件中的AuthServiceProvider调用代码的注释。

AuthServiceProvider存放在app/Providers文件夹中,此文件中只有一个Auth::viaRequest调用。viaRequest会在系统需要认证的时候被调用,此方法接受一个Closure(匿名函数)参数。在此closure(匿名函数)内,你可以任意的解析App\User并返回,或者在解析失败时返回null:

$this->app['auth']->viaRequest('api', function ($request) {
//返回User或者null...
});

同样,你可以使用你期望的方式取得用户认证,比如在请求头或者查询字符串中使用api令牌、请求中的bearer令牌,或者使用应用程序需要的任何其他方法。

如果你的项目没有使用Eloquent,你需要返回一个Illuminate\Auth\GenericUser类的实例。这个类接受一个属性数组作为构造函数的唯一参数:

use Illuminate\Auth\GenericUser;

return new GenericUser(['id' => 1, 'name' => 'Taylor']);

# 缓存 #

### 简介 ###
laravel为各种缓存系统提供了统一的api。缓存配置位于.env文件中。在该文件中你可以指定应用默认使用哪个缓存驱动。laravel支持当前流行的后端缓存,例如memcached和Redis。

不同于laravel

lumen缓存驱动与laravel缓存驱动使用了完全相同的代码。除配置之外,在lumen中使用缓存和在laravel中使用缓存没有区别;因此,请参阅laravel文档来获取使用示例。

注意:在使用cache facade之前,请确保在bootstrap/app.php文件中没有注释掉$app->withFacaes()方法的调用。

Redis支持

在使用lumen的Redis缓存之前,你需要通过composer安装illuminate/redis(5.5.*)包。然后,你需要在bootstrap/app.php文件中注册illuminate\Redis\RedisServiceProvider。

如果你没有在bootstrap/app.php文件中调用$app->withEloquent(),那么你应该在bootstrap/app.php文件中调用$app->configure('database');以确保正确加载Redis数据库配置。

# 缓存 #

### 简介 ###
laravel为各种缓存系统提供了统一的api。缓存配置位于.env文件中。在该文件中你可以指定应用默认使用哪个缓存驱动。laravel支持当前流行的后端缓存,例如Memcached和Redis。

### 不同于laravel ###

lumen缓存驱动与laravel缓存驱动使用了完全相同的代码。除配置之外,在lumen中使用缓存和在laravel中使用缓存没有区别;因此,请参阅laravel文档来获取使用示例。

注意:在使用Cache Facade之前,请确保在bootstrap/app.php文件中没有注释掉$app->withFacdes()方法的调用。

Redis支持

在使用lumen的Redis缓存之前,你需要通过composer安装illuminate/redis(5.5.*)包。然后,你需要在bootstrap/app.php文件中注册illuminate\Redis\RedisServiceProvider。

如果你没有在bootstrap/app.php文件中调用$app->withEloquent()。那么你应该在bootstrap/app.php文件中调用$app->configure();以确保正确加载Redis数据库配置。

# 数据库 #

### 配置 ###

lumen让连接数据库和执行查询变得非常简单。目前lumen支持四种数据库系统:MySQL,Postgres,SQLite和SqlServer。

你可以在.env配置文件中使用DB_*选项配置数据库设置,例如数据库驱动、host、用户名和密码。

### 基本用法 ###

注意:如果你想使用DB facade,你应该去掉在bootstrap/app.php文件中$app->withFacades()的调用的注释。

例如,在不启用facades时,你可以通过app辅助函数连接数据库:

$result = app('db')->select("SELECT * FROM users");

或者,在启用facades后,你可以通过DB facade来连接数据库:

$result = DB::select("SELECT * FROM users");

### 基本查询 ###

要了解如何通过数据库组件执行基本的原始SQL查询,请参考laravel文档

### 查询构造器 ###

lumen同样支持Eloquent ORM的流式查询构造器。要了解这个特性的更多信息,请参阅laravel文档。

### Eloquent ORM ###
如果你喜欢使用Eloquent ORM,你应该去掉bootstrap/app.php文件中对$app->withQloquent()调用的注释。
当然,你可以在lumen中非常容易的使用完整的Eloquent ORM。要了解如何使用Eloquent,请参考laravel文档。

### 迁移 ###

关于如何创建数据库表和执行迁移的更多文档,请参考laravel文档中的迁移。

# 加密与解密 #

## 配置 ##

在使用lumen的加密之前,你应该先把.env文件中APP_KEY选项设置为32位随机字符串。如果没有适当地设置这个值,所有被lumen加密的值都将是不安全的。

### 基本用法 ###

#### 加密一个值 ####

你可以使用crypt门面来加密一个值。所有的加密值都使用OpenSSL和AES-256-CBC来进行加密。此外,所有加密过的值都会使用消息认证码(MAC)来进行签名,以检测加密字符串是否被篡改过:

例如,我们可以使用encrypt方法加密机密信息,并把它保存在Eloquent模型:

fill([
'secret' => Crpty::encrypt($request->secret)
])->save();
}
}

加密一个值

当然,你可以使用crypt门面的decrypt方法来解密值。如果该值不能够被正确的解密。例如当MAC(消息认证码)无效时,就会抛出异常Illuminate \Contracts\Encryption\DecryptException:

use Illuminate\Contracts\Encryption\DecryptException;

try{
$desrypted = Crypt::decrypt($encryptedValue);
}catch (DecryptException $e){
//
}

## 错误与日志 ##

### 简介 ###

当你开始一个新的Lumen项目时,lumen就已经帮你配置好错误和异常处理的操作。另外,lumen也集成了Monolog日志函数库,Monolog支持和提供多种强大的日志处理功能。

有关错误的更多信息,请参阅完整的laravel错误文档。
有关日志的更多信息,请参阅完整的laravel的日志文档。

## 错误处理 ##

#### 介绍 ####

当你启动一个新的laravel项目时,错误及异常处理是已为你配置好了的。
App\Exceptions\Handler类负责记录应用程序触发的所有异常并呈现给用户。在本文档中,我们将深入探讨这个类。

配置

你的config/app.php配置文件中的debug选项决定了对于一个错误实际上讲显示多少信息给用户。默认情况下,该选项的设置将遵循存储在.env文件中的APP_DEBUG环境变量的值。

对于本地开发,你应该将APP_DEBUG环境变量的值设置为true。在生产环境中,该值赢始终未false。如果在生产将该值设置为true,则可能会将敏感配置暴露给应用程序的最终用户。

异常处理器

report方法

所有异常都是由App\Exceptions\Handler类处理。这个类包含两个方法:report和rendor。我们将详细剖析这些方法。report方法用户记录异常或将它们发送给如Bugsnag或者Sentry等外部服务。默认情况下,report方法将异常传递给记录异常的基类。不过,你可以任何自己喜欢的方式来记录异常。

例如,如果你需要以不同方式报告不同类型的异常,则可以使用PHP的instanceof比较运算符:

/**
*报告或者记录异常
*
*此处是发送异常给sentry、Bugsnag等外部服务的好位置。
*
*@param \Exception $exception
*@return void
*/
public function report()
{
if ($exception instance CustomException) {
//
}
return parent::report($exception);
}

{tip}不要在report方法中进行太多的instanceof检查,而应该考虑使用[可报告异常(reportable exception)]
{(/docs/laravel/5.7/errors#renderable-exceptions)}。

### Report辅助函数 ###

有时你可能需要报告异常,但又不希望终止当前请求的处理。report辅助函数允许你使用异常处理器的report方法在不显示错误页面的情况下快速报告异常:

public function isValid()
{
try{
}catch(Exception $e){
report($e);
return false;
}
}

### 按类型忽略异常 ###

异常处理器的$dontReport属性包含一组不会被记录的异常类型。例如,由04错误导致的异常一级其他几种类型的错误不会写入日志文件。你可以根据需要添加其他异常类型到此数组中:

/**
*不应被报告的异常类型清单。
*
*@var array
*/
protected $dontReport = [
\Illuminate\Auth\AuthenticationException::class,
\Illuminate\Auth\Access\AuthorizationException::class,
\Symfony\Component\HttpKernel\Exception\HttpException::class,
\Illuminate\DataBase\Eloquent\ModelNotFoundException::class,
\Illuminate\Validation\ValidationException::class,
];

#### Render方法 ####

Rendor方法负责将给定的异常转换为将被发送回浏览器的HTTP响应。默认值情况下,异常将传递给你生成响应的基类。不过,你可以按自己意愿检查异常类型或者返回自己的自定义响应:

/**
*将异常1转换为HTTP响应。
*
*@param \Illuminate\Http\Request $request
*@param \Illuminate\Exception $exception
*@return \Illuminate\Http\Response
*/
public funtion render($request, Exception $exception)
{
if ($exception instanceof CustomException) {
return response()->view('errors.custom', [], 500);
}

return parent::render($request, $exception);
}

## Reportable & Renderable 异常 ##

除了在异常处理器的report和render方法中检查异常类型,你还可以直接在定义异常上定义report和render方法。当定义了这些方法时,它们会被框架自动调用:

{{ $exception->getMessage() }}

# 日志 #

### 简介 ###
为了帮助你更多的了解应用程序中到底发生了什么,laravel提供了强大的日志服务,允许你将日志消息,系统错误日志记录到文件,甚至使用slack通知到你的整个团队。

在laravel框架中,laravel使用monolog库,它为各种强大的日志处理提供支持。laravel使用配置这些处理程序变得简单,允许泥混合并匹配它们自定义的应用程序日志处理。

### 配置 ###

所有的应用程序日志系统配置都位于config/logging.php配置文件中。这个文件允许你配置你的应用程序日志通信,
所以务必查看每个可用的通信及它们的选项。当然,我们将在下面回顾一些常用的选项。

默认情况下,laravel将使用stack去记录日志消息。stack通道被用来将多个日志通道聚合到一个单一的通道中。关于堆栈的更多信息,查看以下文档。

### 配置通道名称 ###

默认强狂下,monolog使用与当前环境匹配的【通道名称】进行实例化,比如production或者local。要改变这个值,需添加一个name选项到你的通道配置中:

'stack' => [
'driver' => 'stack',
'name' => 'channel-name',
'channels' => ['single', 'slack'],
];

### 可用的通道驱动 ###

名称 描述
single 一个便于创建[多通道]通道的包装器
daily 单个文件或者基于日志通道的路径(StreamHandler)
slack 一个每天轮换的基于monolog驱动的RotatingFileHandler
syslog 一个基于monolog驱动的sysloghandler
errorlog 一个基于monolog驱动的errorlogHandler
monolog 一个可以任何支持monolog处理程序的monolog工厂驱动程序
custom 一个调用指定工厂创建通信通道的驱动程序

{tip}有关monolog和custom驱动,查看高级通道自定义

## 配置slack通道 ##

slack通道需要url配置选项。这个URL应当与你为slack团队配置的一个incoming webhook相匹配。

构建日志栈

如前所述,stack驱动允许你将多个通道合并到一个单一日志通道中。为了说明如何使用日志栈,让我们看一个你可能在生产应用配置中看到的实例配置:

'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['syslog', 'slack'],
],

'syslog' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => 'Laravel Log',
'emoji' => ':boom:',
'level' => 'critical',
],
],

让我们剖析这个配置。首先,注意我们的stack通道通过它的Channels选项聚合其它两个通道:syslog和slack。因此,当记录日志消息时,这两个通道都有机会去记录日志消息。

### 日志级别 ###

注意上面实例中在syslog和slack通道配置中存在的level配置选项。这个选项决定了一个消息必须被通道记录的最小[level]。monolog为laravel的日志服务提供了RFC-542规范中定义的所有日志级别:emergency,
alert,critical,error,warning,notice,info和debug。

因此,假设我们记录一个日志消息使用debug方法:

Log::debug('An informational message.');

根据我们的配置,syslog通道将写消息到系统日志;然而,由于错误消息不是critical或者这个级别之上,它将不被发送到slack。但是,如果我们记录一个emergency消息,它将被同时发送到系统日志和slack,因为emergency级别高于我们对两个通道最低级别的阈值:

#### Log::emergency('The system is down!'); ####

### 记录日志消息 ###

你可以通过Log外观类将信息写入到日志。如前所述,日志器提供在RPC 5424规范中定义的八个日志级别:

emergency,alert,critical,error,warning,notice,info和debug:

Log::emergency($message);
Log::alert($message);
Log::critical($message);
Log::error($mesage);
Log::warning($message);
Log::notice($message);
Log::info($message);
Log::debug($message);

因此,你可以调用这些方法中的任何一个去记录相应级别的一个日志消息。默认情况下,消息将写入到你的config/logging.php配置文件配置的默认日志通道中:
User::findOrFail($id)]);
}
}

## 记录日志到指定通道 ##

有时候你可能希望将日志记录到非默认通道。你可以使用log facade中的channel方法,将日志记录到应用配置中存在的任何聚到:

Log::channel('stack')->info('Something happened!');

如果你想按需要创建多个渠道的日志堆栈,你可以使用stack方法:

Log::stack(['single', 'slack'])->info("Something happened!");

先进的monolog日志通道定制

自定义monolog日志通道

首先你可能需要完全配置monolog现有的通道。例如:你想要为现有通道自定义一个monolog formatterInterface实现。

首先,在频道配置文件中定义一个tap数组。tap数组应该该包含所需的类列表,这些类就是Monolog实例创建后需要自定义(或者开发)的类:

'single' => [
'driver' => 'single',
'tap' => [],
],

## 事件 ##

#### 简介 ####
lumen事件提供了简单的监听器实现,允许你订阅和监听事件,事件类通常被保存在app/Events目录下,而它们的侦听器被保存在app/Listeners目录下。

### 与laravel的差异 ###

通常,lumen中的事件方法恰好与laravel全栈框架功能一致,所以,请阅读完整的laravel文档。lumen同样支持事件广播,它允许客户端的JavaScript监听服务器的事件。然而,这里还是有些差异值得谈论。

### 生成器 ###

lumen中没有可以用来生成事件和监听器的命令,你可以通过简单赋值ExampleEvent或者ExampleListener文件来定义你自己的事件和监听器,这两个示例文件提供了每个事件和监听器的基础类结构。

#### 注册事件或者监听器 ####

像laravel框架一样,lumen应用内置的EventServiceProvider提供了一个注册所有事件监听器的地方。listen属性一个数组,它包含了所有的事件(键)和监听器(值)。所以,你可以根据应用程序的需要添加事件到这个数组:

/**
*应用程序的事件监听器映射。
*
*@var array
*/
protected $listen = [
'App\Events\ExampleEvent => [
'App\Listeners\ExampleListener',
],
];

### 触发事件 ###

你可以使用event辅助函数或者event门面在lumen应用程序中触发事件。同样,这些函数的行为与laravel框架一致:

event(new ExampleEvent);

Event::dispatch(new ExampleEvent);

### 队列 ###

#### 简介 ####

lumen的队列服务给不同的后端队列提供统的API。队列允许你延迟处理消失的任务,例如在远程服务器上执行任务直到更晚的时间,而同一时间你的应用程序可以快速的处理web应用程序的请求。

就像该框架的许多其他部分一样,lumen的队列服务跟laravel的队列服务功能相同。因此,如果要了解更多关于lumen的队列,则可以参阅laravel消息队列文档。

### 配置 ###

队列配置选项对话都在.env文件中。

如果您想要完全自定义的配置,必须您将vendor/laravel/lumen-framework/config/queue.php文件完整的复制到你的项目目录根中config目录,并且要调整必要的配置。如果config目录不存在,则要创建。

### 驱动程序先决条件 ###

#### 数据库 ####

使用为了database队列驱动程序,您将需要数据库表保存作业和失败情况:

Schema::create('jobs', function (Blueprint $table) {
$table->bigIncrement('id');
$table->string('queue');
$table->longText('payload');
$table->tinyInteger('attempts')->unsigned();
$table->unsignedInteger('reserved_at')->nullable();
$table->unsignedInteger('available_at');
$table->unsignedInteger('created_at');
$table->index(['queue', 'reserved_at']);
});

Schema::create('failed_jobs', function (Blueprint $table) {
$table->increment('id');
$table->text('connection');
$table->text('queue');
$table->longText('payload');
$table->longText('exception');
$table->timestamp('failed_at')->useCurrent();
});

### Redis的 ###

要想使用Redis队列驱动程序,需要先通过composer安装illuminate/redis(5.5.*)扩展包。然后再bootstrap/app.php文件中注册Illuminate\Redis\RedisServiceProvider;

### 其他队列驱动程序的依赖包 ###

下面列出其他队列驱动程序所需要的依赖扩展包:
亚马逊SQS:aws/aws-sdk-php ~3.0
Beanstalked:pda/pheanstalk ~3.0

#### 与laravel的差异 ####

与框架的许多其他部分一样,lumen队列作业的功能与laravel的队列作业功能相同。因此,要了解lumen队列作业功能,请查看完整的laravel队列文档。

不过呢,我们现在讲讨论两个框架间的一些细微差异。首先,我们来谈谈lumen中如何生成队列作业。

#### 生成器 ####

lumen不包括用于自动创建新job类的生成器。因此你需要赋值框架所带的exampleJob类。这个类提供了每个job类共享的基本结构。examplejob所继承的job基类已包含所需的interactswithqueue,queueable和serializesmodels特性:

withFacades()调用的注释:

Queue::push(new ExampleJob);

### 服务容器: ###

#### 简介 ####

laravel的服务容器是一个管理类依赖和执行依赖注入的强力工具。依赖注入是个花俏的名字,事实上是指:类的依赖通过构造器或者在某些情况下通过【setter】方法【注入】。

### 与laravel的差异 ###

lumen使用了与laravel框架相同的服务器。所以,你可以使用它们所有强大的功能。有关容易的完整文档,请阅读laravel容器文档。

#### 获取服务容器 ####
laravel\lumen\application实例是Illuminate\Container\Container的扩展,所以你可以当做服务容器来使用。
通常我们会在服务提供者注册我们的容器解析规则。当然,你可以使用bind、singleton’instance、以及容器提供的其他方法。请记住,所有这些方法都记录在laravel服务器容易文档中。
解析实例

想要从服务容器中解析实例,你可以在大部分的功能类里自动解析(依赖注入),如路由closure,控制器的构造方法、控制器方法、中间件、事件监听器,或者队列等。或者,你也可以在应用程序中的任何地方使用App函数来进行解析:

$instance = app(Something::class);

# 服务提供者 #

简介
服务提供者是所有lumen应用程序启动的中心所在。包括你自己的应用程序,以及所有的核心服务,都是服务提供者启动的。

但是,我们所说的启动值得是什么?一般而言,我们指的是注册事物,包括注册服务容易绑定、事件侦听器、中间件,甚至路由、服务提供者设置你的应用程序的中心所在。

若你打开lumen的bootstrap/app.php文件,你将会看到$app->register()方法的调用。你也许需要额外的调用来注册你的服务提供者。

### 编写服务提供者 ###

所有的服务提供者都集成了illuminate\Support\ServiceProvider这个类。这个抽象类要求你在你的提供者上定义至少一个方法:register。在register方法内,你应该只需要将事物绑定到服务容器中。永远不要试图在register方法中注册任何事件侦听器,路由或者任何其他功能。

注册方法

如前面所讲,在register方法中,你只要将事物绑定到【服务容器中】。永远不要试图在register方法中注册任何事件侦听器、路由或者任何其它功能。否则,你有可能会以外的使用到尚未加载的服务提供者提供的服务。

现在,让我们来看一个基本的服务提供者代码:

app->singleton(Connection::class, function ($app) {
return new Connection(config('riak'));
});
}
}

这个服务提供者自定义了一个register方法,并且用这个方法在服务容器中绑定了Riak\Connection的一个实例。如果你不是很了解服务容器的运行原理,请查看[its documentation]。

### 启动方法 ###

那么,如果我们要在服务提供者当中注册一个视图组件呢?这应该在boot方法内完成。此方法在所有其他服务提供者都注册之后才能调用,也就意味着可以访问已经被框架注册的所有服务:

register()方法调用。你也行需要额外的调用$app->register()来注册你的服务提供者。

# 测试 #

### 简介 ###

lumen在创建时就已考虑到测试部分。事实上,lumen默认就支持用PHPunit来做测试,并为你的应用程序创建好了phpunit.xml文件。框架还提供了一些便利的辅助函数,让你可以更直观的测试应用程序的json响应。

## 测试环境 ##

在运行测试时,lumen自动配置讲缓存驱动配置为array,意味着在测试的时候不会保存任何的缓存数据。

你可以随意创建其他必要的测试配置环境。testing的环境变量可以在phpunit.xml文件中进行修改;

## 定义和运行测试 ##

要创建一个测试用例,直接将新的测试文件创建到tests文件夹下即可。测试文件必须继承TestCase。接着就可以像平常使用PHPunit一样来定义测试方法。要运行测试只需要在命令行上运行PHPunit命令即可:

assertTrue(true);
}
}

注意:如果要在你的类自定义setUp方法,请确保调用了parent::setUp。

## 应用测试 ##

lumen提供了一个非常好用的api,使用它用来向你的应用发起HTTP请求,并查看输出结果。

#### 测试json api接口 ####

lumen同样提供了几个测试用于测试json api接口和响应数据的助手。例如,get,post,put,patch和delete方法可以被用于发起各种HTTP请求方式,并且声明以json格式返回一个指定的数组:

json('POST', '/user', ['name' => 'Sally']);
->seeJson([
'created' => true,
]);
}
}

seeJson方法数组转换为json,并验证这个json片段发生在应用返回的整个json响应的任意位置。所以,即使在json响应中存在其他属性,只要指定的片段存在,这个测试任然会成功!

验证完全匹配的json

如果你想验证传入的数组是否与应用程序返回的json完全匹配,你可以用seeJsonEquals方法:
post('/user', ['name' => 'Sally'])
->seeJsonEquals([
'created' => true,
]);
}
}

### 认证 ###

actingAs辅助函数提供了简单的方式来让指定的用户认证为当前的用户:
create();
$this->actingAs($user)
->get('/user');
}
}

自定义HTTP请求

如果你想要创建一个自定义的HTTP请求到应用程序上,并获取完整的 Illuminate\Http\Response对象,可以使用call方法:

public function testApplication()
{
$response = $this->call('GET', '/');
$this->assertEquals(200, $response->status());
}
如果你想构造POST,PUT,或者PATCH请求,可以在请求时传入一个数组作为请求参数,当然,你也可以在路由及控制器中通过请求实例来获取传过来的参数:

$response = $this->call('POST', '/user', ['name' => 'Taylor']);

使用数据库

为了使得测试使用了数据库的应用更加简便,lumen提供了各种有用的工具。首先,你可以使用seeInDatabase助手函数来断言数据库中是否存在给定条件的数据。例如,我们想要验证users表中的有一条email的值为Sally@example.com的记录,我们可以按如下操作:

public function testDatabse()
{
//Make call to application...

$this->seeInDatabase('users', ['email' => 'sally@foo.com']);
}

当然,seeInDatabase方法和类似的助手方法就是会为了方便使用。你也可以在测试中*使用PHPunit的内置断言方法。

### 每次测试之后重置数据库 ###

在每次测试后重置数据库时非常有必要的,这样之前的测试数据不会响应后面的测试。

#### 使用迁移 ####

有一个选择是每次测试之后回滚数据库,并且在下一次测试之前将其迁移。lumen提供了一个简单的DatabaseMigrations特性,它可以自动位您处理。简单的在您的测试类中运用这个特性如下:

get('/foo');
}
}

## 使用事务 ##

另一个选择是将每一个测试用例包装在数据库事务中。撸们提供了便利的DatabaseTransactions特性,可以为您自动的执行这些:

define('App\User', function ($faker) {
return [
'name' => $faker->name,
'email' => $faker->email,
];
});

在作为工厂定义的闭包中,您可以返回模型上所有属性的默认测试值。闭包将接收Faker PHP库的一个实例,它将允许您便利的生成各种随机数据以方便测试。

当然,您可以将您自己的额外的工厂添加到ModelFactory.php文件中。

## 多种工厂类型 ##

有时您可能希望同一个Eloquent模型有多种工厂。例如,可能您会希望除了普通的用户之外还有管理员用户的工厂。您可能会使用defineAs方法定义这些工厂:

$factory->defineAs('App\User', 'admin', function ($faker) {
return [
'name' => $faker->name,
'email' => $faker->email,
'admin' => true,
];
});

您可以使用raw方法检索其基本属性,而不是复制基本用户工厂中的所有属性。一旦拥有这些属性,只需要使用您需要的任何附加值补充它们:

$factory->defineAs('App\Users', 'admin', function ($faker) use ($factory) {
$user = $factory->raw('App\User');
return array_merge($user, ['admin' => true]);
});

## 在测试中使用工厂 ##

在工厂定以后,就可以在测试或者是数据库的填充文件中,通过全局的factory函数来生成模型实例。接着让我们先来看看几个创建模型的例子。首先我们会使用make方法创建模型,但不将他们保存至数据库:
public function testDatabase()
{
$user = factory('App\User')->make();
}

如果你想要写模型中的某些默认值,则可以传递一个包含数值的数组至make方法。只有指定的数值会被替换,其他剩余的数值则会安装工程指定的默认值来设置:

$user = factory('App\User')->make([
'name' => 'Abigail',
]);

你还可以创建许多模型的集合或者创建给定类型的模型:

//Create three App\User isntance...

$users = factory('App\User', 3)->make();

//Create an App\User "admin" instance...
$user = factory('App\User', 'admin')->make();

//Create three App\User "admin" instances...
$users = factory("App\User", 'admin', 3)->make();

维持工厂模式
可以使用create方法创建模型实例,还可以使用save方法将数据保存到数据库:

public function testDatabase()
{
$user = factory("App\User")->create();
// Use model in tests...
}

同样,你也可以使用数组的方式使用create方法将数据写入模型

$user = factory('App\User')->create([
'name' => 'abigail',
]);

添加关联至模型

你甚至可以保存多个模型到数据库上。在本例中,我们还会增加关联至我们所创建的模型。当使用create方法创建多个模型时,它会返回一个Eloquent集合实例,让你能使用集合提供的便利方法,例如each方法:
$users = factory('App\User', 3)
->create()
->each(function ($u) {
$u->posts()->save(factory('App\Posts')->make);
});

模拟
模拟事件
如果你大量地使用lumen的事件系统,你可能会希望在测试停止或者模拟某些事件。例如,如果你在测试你的注册功能,你可能不希望所有的UserRegistered事件被触发,因为它们会触发“欢迎”邮件的发送。

lumen提供了简单的expectsEvents方法,以验证预期的事件有没有被运行,可防止该事件的任何处理进程被进行:
expectsEvents('App\Events\UserRegistered');
//测试用户注册功能...
}
}

如果你想阻止所有的事件处理程序运行,你可以使用withoutEvents方法:

withoutEvents();
//Test user registraction code。。。
}
}

模拟任务

有时你可能希望当请求发送至应用程序时,简单地对控制器所派送的任务进行测试。这么做能够让你隔离测试路由或者控制器,设置除了任务以外的逻辑。当然,在此之后你也可以在一个单独的测试案例中测试该任务。

lumen提供了一个简便的expectsJob方法,以验证预期的任务有没有被派送,但任务本身不会被运行:

expectsJobs('App\Jobs\PurchasePodcast');
//测试购买博客代码...
}
}

注意:该方法只检测通过全局猪手函数dispatch或者路由或控制器中的$this->dispatch方法派送的任务。它并不会检测被直接发送到Queue::push的任务。

模拟facades

在测试时,经常需要模拟对lumen facade的调用。例如,考虑如下控制器的操作:
once()
->with('key')
->andReturn('value');

$this->get('/users');
}
}

注意:你不应该模拟Request门面。应该在测试时使用如call及post这样的HTTP辅助函数来传递你想要的数据。

# 数据验证 #

## 简介 ##

lumen提供了数种不同的方法来验证传入应用程序的数据。默认情况下,lumen的基本控制器类使用名为ProvidesConvenienceMethods的trait,其提供了一种便捷的方法来使用各种强大的验证规则验证传入的HTTP请求。

一般来说,lumen中的数据验证与laravel中的数据验证并无多大区别,因此你应该查过完整的laravel数据验证文档以熟悉其使用;不过,它们之间也存在少许重要的差异。

# 与laravel的差异 #

## 表单请求 ##

lumen不支持表单请求。如果想使用表单请求,则赢使用完整的laravel框架。

$this->validate方法

在lumen中可用的$this->validate辅助方法将始终返回带有相关错误消息的json响应。而该方法的laravel版本,如果请求不是ajax请求,返回的则是重定向响应。由于lumen是无状态的,且不支持会话,所以闪存错误信息在会话中是不可能的。如果想要使用重定向及闪存错误数据,应该使用完整的laravel框架。

与laravel不同的是,lumen支持在Route闭包中访问validate方法:

use Illuminate\Http\Request;

$router->post('/user', function (Request $request) {
$this->validate($request, [
'name' => 'required',
'email' => 'required|email|unique:users',
]);

//存储用户
});

当然,你可以*地使用Validator::make facade方法手动创建验证器实例,就像在laravel中一样。

exists和unique规则

如果想要使用exists或者unique验证规则,则应该在bootstrap/app.php文件中取消$app->withEloquent()方法调用的注释。

视图变量$errors

lumen不支持session,因此在laravel中每个视图都可用的$errors视图变量在lumen中式不可用的。如果验证失败,那么$this->validate辅助方法会抛出Illuminate\ValidationException异常,期中嵌入了包含所有相关错误消息的json响应,如果你并非只构建仅发送json响应的无状态api,则应使用完整的laravel框架。tongzhuo_examination_db