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