服务 ―― 验证
最后更新于:2022-04-01 04:11:43
# 服务 —— 验证
## 1、简介
Laravel提供了多种方法来验证应用输入数据。默认情况下,Laravel的控制器基类使用`ValidatesRequests` trait,该trait提供了便利的方法通过各种功能强大的验证规则。
## 2、快速入门
要学习Laravel强大的验证特性,让我们先看一个完整的验证表单并返回错误信息给用户的例子。
### 2.1 定义路由
首先,我们假定在`app/Http/routes.php`文件中包含如下路由:
~~~
// 显示创建博客文章表单...
Route::get('post/create', 'PostController@create');
// 存储新的博客文章...
Route::post('post', 'PostController@store');
~~~
当然,GET路由为用户显示了一个创建新的博客文章的表单,POST路由将新的博客文章存储到数据库。
### 2.2 创建控制器
接下来,让我们看一个处理这些路由的简单控制器示例。我们先将`store`方法留空:
~~~
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class PostController extends Controller{
/**
* 显示创建新的博客文章的表单
*
* @return Response
*/
public function create()
{
return view('post.create');
}
/**
* 存储新的博客文章
*
* @param Request $request
* @return Response
*/
public function store(Request $request)
{
// 验证并存储博客文章...
}
}
~~~
### 2.3 编写验证逻辑
现在我们准备用验证新博客文章输入的逻辑填充`store`方法。如果你检查应用的控制器基类(`App\Http\Controllers\Controller`),你会发现该类使用了`ValidatesRequests` trait,这个trait在所有控制器中提供了一个便利的`validate`方法。
`validate`方法接收一个HTTP请求输入数据和验证规则,如果验证规则通过,代码将会继续往下执行;然而,如果验证失败,将会抛出一个异常,相应的错误响应也会自动发送给用户。在一个传统的HTTP请求案例中,将会生成一个重定向响应,如果是AJAX请求则会返回一个JSON响应。
要更好的理解validate方法,让我们回到store方法:
~~~
/**
* 存储博客文章
*
* @param Request $request
* @return Response
*/
public function store(Request $request){
$this->validate($request, [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);
// 验证通过,存储到数据库...
}
~~~
正如你所看到的,我们只是传递输入的HTTP请求和期望的验证规则到`validate`方法,在强调一次,如果验证失败,相应的响应会自动生成。如果验证通过,控制器将会继续正常执行。
### 2.3.1 嵌套属性注意事项
如果HTTP请求中包含“嵌套”参数,可以使用“.”在验证规则中指定它们:
~~~
$this->validate($request, [
'title' => 'required|unique:posts|max:255',
'author.name' => 'required',
'author.description' => 'required',
]);
~~~
### 2.4 显示验证错误信息
那么,如果请求输入参数没有通过给定验证规则怎么办?正如前面所提到的,Laravel将会自动将用户重定向回上一个位置。此外,所有验证错误信息会自动[一次性存放到session](http://laravelacademy.org/post/230.html#ipt_kb_toc_230_5)。
注意我们并没有在GET路由中明确绑定错误信息到视图。这是因为Laravel总是从session数据中检查错误信息,而且如果有的话会自动将其绑定到视图。所以,值得注意的是每次请求的所有视图中总是存在一个**`$errors`**变量,从而允许你在视图中方便而又安全地使用。`$errors`变量是的一个`Illuminate\Support\MessageBag`实例。想要了解更多关于该对象的信息,[查看其文档](http://laravelacademy.org/post/240.html#working-with-error-messages)。
所以,在我们的例子中,验证失败的话用户将会被重定向到控制器的create方法,从而允许我们在视图中显示错误信息:
~~~
<!-- /resources/views/post/create.blade.php -->
<h1>Create Post</h1>
@if (count($errors) > 0)
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<!-- Create Post Form -->
~~~
### 2.5 AJAX请求&验证
在这个例子中,我们使用传统的表单来发送数据到应用。然而,很多应用使用AJAX请求。在AJAX请求中使用`validate`方法时,Laravel不会生成重定向响应。取而代之的,Laravel生成一个包含验证错误信息的JSON响应。该JSON响应会带上一个HTTP状态码`422`。
## 3、其它验证方法
### 3.1 手动创建验证器
如果你不想使用`ValidatesRequests` trait的`validate`方法,可以使用`Validator`门面手动创建一个验证器实例,该门面上的`make`方法用于生成一个新的验证器实例:
~~~
<?php
namespace App\Http\Controllers;
use Validator;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class PostController extends Controller{
/**
* 存储新的博客文章
*
* @param Request $request
* @return Response
*/
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);
if ($validator->fails()) {
return redirect('post/create')
->withErrors($validator)
->withInput();
}
// 存储博客文章...
}
}
~~~
传递给`make`方法的第一个参数是需要验证的数据,第二个参数是要应用到数据上的验证规则。
检查请求是够通过验证后,可以使用`withErrors`方法将错误数据一次性存放到session,使用该方法时,`$errors`变量重定向后自动在视图间共享,从而允许你轻松将其显示给用户,`withErrors`方法接收一个验证器、或者一个MessageBag,又或者一个PHP数组。
### 3.1.1 命名错误包
如果你在单个页面上有多个表单,可能需要命名MessageBag,从而允许你为指定表单获取错误信息。只需要传递名称作为第二个参数给`withErrors`即可:
~~~
return redirect('register')
->withErrors($validator, 'login');
~~~
然后你就可以从`$errors`变量中访问命名的MessageBag实例:
~~~
{{ $errors->login->first('email') }}
~~~
### 3.1.2 验证钩子之后
验证器允许你在验证完成后添加回调,这种机制允许你轻松执行更多验证,甚至添加更多错误信息到消息集合。使用验证器实例上的`after`方法即可:
~~~
$validator = Validator::make(...);
$validator->after(function($validator) {
if ($this->somethingElseIsInvalid()) {
$validator->errors()->add('field', 'Something is wrong with this field!');
}
});
if ($validator->fails()) {
//
}
~~~
### 3.2 表单请求验证
对于更复杂的验证场景,你可能想要创建一个“表单请求”。表单请求是包含验证逻辑的自定义请求类,要创建表单验证类,可以使用Artisan命令`make:request`:
~~~
php artisan make:request StoreBlogPostRequest
~~~
生成的类位于`app/Http/Requests`目录下,接下来我们添加少许验证规则到`rules`方法:
~~~
/**
* 获取应用到请求的验证规则
*
* @return array
*/
public function rules(){
return [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
];
}
~~~
那么,验证规则如何生效呢?你所要做的就是在控制器方法中类型提示该请求。表单输入请求会在控制器方法被调用之前被验证,这就是说你不需要将控制器和验证逻辑杂糅在一起:
~~~
/**
* 存储输入的博客文章
*
* @param StoreBlogPostRequest $request
* @return Response
*/
public function store(StoreBlogPostRequest $request){
// The incoming request is valid...
}
~~~
如果验证失败,重定向响应会被生成并将用户退回上一个位置,错误信息也会被一次性存储到session以便在视图中显示。如果是AJAX请求,带`422`状态码的HTTP响应将会返回给用户,该响应数据中还包含了JSON格式的验证错误信息。
### 3.2.1 认证表单请求
表单请求类还包含了一个`authorize`方法,你可以检查认证用户是否有资格更新指定资源。例如,如果用户尝试更新一个博客评论,那么他是否是评论的所有者呢?举个例子:
~~~
/**
* 判断请求用户是否经过认证
*
* @return bool
*/
public function authorize(){
$commentId = $this->route('comment');
return Comment::where('id', $commentId)
->where('user_id', Auth::id())->exists();
}
~~~
注意上面这个例子中对`route`方法的调用。该方法赋予用户访问被调用路由URI参数的权限,比如下面这个例子中的`{comment}`参数:
~~~
Route::post('comment/{comment}');
~~~
如果`authorize`方法返回`false`,一个包含`403`状态码的HTTP响应会自动返回而且控制器方法将不会被执行。
如果你计划在应用的其他部分包含认证逻辑,只需在`authorize`方法中简单返回`true`即可:
~~~
/**
* 判断请求用户是否经过认证
*
* @return bool
*/
public function authorize(){
return true;
}
~~~
### 3.2.2 自定义一次性错误格式
如果你想要自定义验证失败时一次性存储到session中验证错误信息的格式,重写请求基类(`App\Http\Requests\Request`)中的`formatErrors`方法即可。不要忘记在文件顶部导入`Illuminate\Contracts\Validation\Validator`类:
~~~
/**
* {@inheritdoc}
*/
protected function formatErrors(Validator $validator){
return $validator->errors()->all();
}
~~~
## 4、处理错误信息
调用Validator实例上的`errors`方法之后,将会获取一个`Illuminate\Support\MessageBag`实例,该实例中包含了多种处理错误信息的便利方法。
**获取某字段的第一条错误信息**
要获取指定字段的第一条错误信息,可以使用`first`方法:
~~~
$messages = $validator->errors();
echo $messages->first('email');
~~~
**获取指定字段的所有错误信息**
如果你想要简单获取指定字段的所有错误信息数组,使用`get`方法:
~~~
foreach ($messages->get('email') as $message) {
//
}
~~~
**获取所有字段的所有错误信息**
要获取所有字段的所有错误信息,可以使用`all`方法:
~~~
foreach ($messages->all() as $message) {
//
}
~~~
**判断消息中是否存在某字段的错误信息**
~~~
if ($messages->has('email')) {
//
}
~~~
**获取指定格式的错误信息**
~~~
echo $messages->first('email', '<p>:message</p>');
~~~
**获取指定格式的所有错误信息**
~~~
foreach ($messages->all('<li>:message</li>') as $message) {
//
}
~~~
### 4.1 自定义错误信息
如果需要的话,你可以使用自定义错误信息替代默认的,有多种方法来指定自定义信息。首先,你可以传递自定义信息作为第三方参数给`Validator::make`方法:
~~~
$messages = [
'required' => 'The :attribute field is required.',
];
$validator = Validator::make($input, $rules, $messages);
~~~
在本例中,`:attribute`占位符将会被验证时实际的字段名替换,你还可以在验证消息中使用其他占位符,例如:
~~~
$messages = [
'same' => 'The :attribute and :other must match.',
'size' => 'The :attribute must be exactly :size.',
'between' => 'The :attribute must be between :min - :max.',
'in' => 'The :attribute must be one of the following types: :values',
];
~~~
### 4.1.1 为给定属性指定自定义信息
有时候你可能只想为特定字段指定自定义错误信息,可以通过”.”来实现,首先指定属性名,然后是规则:
~~~
$messages = [
'email.required' => 'We need to know your e-mail address!',
];
~~~
### 4.1.2 在语言文件中指定自定义消息
在很多案例中,你可能想要在语言文件中指定属性特定自定义消息而不是将它们直接传递给`Validator`。要实现这个,添加消息到`resources/lang/xx/validation.php`语言文件的custom数组:
~~~
'custom' => [
'email' => [
'required' => 'We need to know your e-mail address!',
],
],
~~~
## 5、有效验证规则
下面是有效规则及其函数列表:
### accepted
在验证中该字段的值必须是`yes`、`on`、`1`或`true`,这在“同意服务协议”时很有用。
### active_url
该字段必须是一个基于PHP函数`checkdnsrr` 的有效URL
### after:date
该字段必须是给定日期后的一个值,日期将会通过PHP函数`strtotime`传递:
~~~
'start_date' => 'required|date|after:tomorrow'
~~~
你可以指定另外一个比较字段而不是使用strtotime验证传递的日期字符串:
~~~
'finish_date' => 'required|date|after:start_date'
~~~
### alpha
该字段必须是字母
### alpha_dash
该字段可以包含字母和数字,以及破折号和下划线
### alpha_num
该字段必须是字母或数字
### array
该字段必须是PHP数组
### before:date
验证字段必须是指定日期之前的一个数值,该日期将会传递给PHP `strtotime`函数。
### between:min,max
验证字段尺寸在给定的最小值和最大值之间,字符串、数值和文件都可以使用该规则
### boolean
验证字段必须可以被转化为`boolean`,接收`true`, `false`, `1`,`0`, `"1"`, 和 `"0"`等输入。
### confirmed
验证字段必须有一个匹配字段`foo_confirmation`,例如,如果验证字段是`password`,必须输入一个与之匹配的`password_confirmation`字段
### date
验证字段必须是一个基于PHP `strtotime`函数的有效日期
### date_format:format
验证字段必须匹配指定格式,该格式将使用PHP函数`date_parse_from_format`进行验证。你应该在验证字段时使用`date`或`date_format`
### different:field
验证字段必须是一个和指定字段不同的值
### digits:value
验证字段必须是数字且长度为`value`指定的值
### digits_between:min,max
验证字段数值长度必须介于最小值和最大值之间
### email
验证字段必须是格式化的电子邮件地址
### exists:table.column
验证字段必须存在于指定数据表
**基本使用:**
~~~
'state' => 'exists:states'
~~~
**指定自定义列名:**
~~~
'state' => 'exists:states,abbreviation'
~~~
**还可以添加更多查询条件到`where`查询子句:**
~~~
'email' => 'exists:staff,email,account_id,1'
~~~
**传递NULL作为`where`子句的值将会判断数据库值是否为NULL:**
~~~
'email' => 'exists:staff,email,deleted_at,NULL'
~~~
### image
验证文件必须是图片(jpeg、png、bmp、gif或者svg)
### in:foo,bar…
验证字段值必须在给定的列表中
### integer
验证字段必须是整型
### ip
验证字段必须是IP地址
### max:value
验证字段必须小于等于最大值,和字符串、数值、文件字段的size规则一起使用
### mimes:foo,bar,…
验证文件的MIMIE类型必须是该规则列出的扩展类型中的一个
MIMIE规则的基本使用:
~~~
'photo' => 'mimes:jpeg,bmp,png'
~~~
### min:value
验证字段的最小值,和字符串、数值、文件字段的size规则一起使用
### not_in:foo,bar,…
验证字段值不在给定列表中
### numeric
验证字段必须是数值
### regex:pattern
验证字段必须匹配给定正则表达式
> 注意:使用`regex`模式时,规则必须放在数组中,而不能使用管道分隔符,尤其是正则表达式中使用管道符号时。
### required
验证字段时必须的
### required_if:anotherfield,value,…
验证字段在另一个字段等于指定值value时是必须的
### required_with:foo,bar,…
验证字段只有在任一其它指定字段存在的话才是必须的
### required_with_all:foo,bar,…
验证字段只有在所有指定字段存在的情况下才是必须的
### required_without:foo,bar,…
验证字段只有当任一指定字段不存在的情况下才是必须的
### required_without_all:foo,bar,…
验证字段只有当所有指定字段不存在的情况下才是必须的
### same:field
给定字段和验证字段必须匹配
### size:value
验证字段必须有和给定值相value匹配的尺寸,对字符串而言,`value`是相应的字符数目;对数值而言,`value`是给定整型值;对文件而言,`value`是相应的文件字节数
### string
验证字段必须是字符串
### timezone
验证字符必须是基于PHP函数`timezone_identifiers_list`的有效时区标识
### unique:table,column,except,idColumn
验证字段在给定数据表上必须是唯一的,如果不指定`column`选项,字段名将作为默认`column`。
**指定自定义列名:**
~~~
'email' => 'unique:users,email_address'
~~~
**自定义数据库连接**
有时候,你可能需要自定义验证器生成的数据库连接,正如上面所看到的,设置`unique:users`作为验证规则将会使用默认数据库连接来查询数据库。要覆盖默认连接,在数据表名后使用”.“指定连接:
~~~
'email' => 'unique:connection.users,email_address'
~~~
**强制一个唯一规则来忽略给定ID:**
有时候,你可能希望在唯一检查时忽略给定ID,例如,考虑一个包含用户名、邮箱地址和位置的”更新属性“界面,当然,你将会验证邮箱地址是唯一的,然而,如果用户只改变用户名字段而并没有改变邮箱字段,你不想要因为用户已经拥有该邮箱地址而抛出验证错误,你只想要在用户提供的邮箱已经被别人使用的情况下才抛出验证错误,要告诉唯一规则忽略用户ID,可以传递ID作为第三个参数:
~~~
'email' => 'unique:users,email_address,'.$user->id
~~~
**添加额外的`where`子句:**
还可以指定更多条件给`where`子句:
~~~
'email' => 'unique:users,email_address,NULL,id,account_id,1'
~~~
### url
验证字段必须是基于PHP函数`filter_var`过滤的的有效URL
## 6、添加条件规则
在某些场景下,你可能想要只有某个字段存在的情况下运行验证检查,要快速完成这个,添加`sometimes`规则到规则列表:
~~~
$v = Validator::make($data, [
'email' => 'sometimes|required|email',
]);
~~~
在上例中,email字段只有存在于`$data`数组时才会被验证。
**复杂条件验证**
有时候你可能想要基于更复杂的条件逻辑添加验证规则。例如,你可能想要只有在另一个字段值大于`100`时才要求一个给定字段是必须的,或者,你可能需要只有当另一个字段存在时两个字段才都有给定值。添加这个验证规则并不是一件头疼的事。首先,创建一个永远不会改变的静态规则到Validator实例:
~~~
$v = Validator::make($data, [
'email' => 'required|email',
'games' => 'required|numeric',
]);
~~~
让我们假定我们的web应用服务于游戏收集者。如果一个游戏收集者注册了我们的应用并拥有超过`100`个游戏,我们想要他们解释为什么他们会有这么多游戏,例如,也许他们在运营一个游戏二手店,又或者他们只是喜欢收集。要添加这种条件,我们可以使用Validator实例上的`sometimes`方法:
~~~
$v->sometimes('reason', 'required|max:500', function($input) {
return $input->games >= 100;
});
~~~
传递给`sometimes`方法的第一个参数是我们需要有条件验证的名称字段,第二个参数是我们想要添加的规则,如果作为第三个参数的闭包返回`true`,规则被添加。该方法让构建复杂条件验证变得简单,你甚至可以一次为多个字段添加条件验证:
~~~
$v->sometimes(['reason', 'cost'], 'required', function($input) {
return $input->games >= 100;
});
~~~
> 注意:传递给闭包的`$input`参数是`Illuminate\Support\Fluent`的一个实例,可用于访问输入和文件。
## 7、自定义验证规则
Laravel提供了多种有用的验证规则;然而,你可能还是想要指定一些自己的验证规则。注册验证规则的一种方法是使用`Validator`[门面](http://laravelacademy.org/post/97.html)的extend方法。让我们在[服务提供者](http://laravelacademy.org/post/91.html)中使用这种方法来注册一个自定义的验证规则:
~~~
<?php
namespace App\Providers;
use Validator;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider{
/**
* 启动应用服务
*
* @return void
*/
public function boot()
{
Validator::extend('foo', function($attribute, $value, $parameters) {
return $value == 'foo';
});
}
/**
* 注册服务提供者
*
* @return void
*/
public function register()
{
//
}
}
~~~
自定义验证器闭包接收三个参数:要验证的属性名称,属性值和传递给规则的参数数组。
你还可以传递类和方法到`extend`方法而不是闭包:
~~~
Validator::extend('foo', 'FooValidator@validate');
~~~
**定义错误信息**
你还需要为自定义规则定义错误信息。你可以使用内联自定义消息数组或者在验证语言文件中添加条目来实现这一目的。消息应该被放到数组的第一维,而不是在只用于存放属性指定错误信息的custom数组内:
~~~
"foo" => "Your input was invalid!",
"accepted" => "The :attribute must be accepted.",
// 验证错误信息其它部分...
~~~
当创建一个自定义验证规则时,你可能有时候需要为错误信息定义自定义占位符,可以通过创建自定义验证器然后调用`Validator`门面上的`replacer`方法来实现。可以在[服务提供者](http://laravelacademy.org/post/91.html)的`boot`方法中编写代码:
~~~
/**
* 启动应用服务
*
* @return void
*/
public function boot(){
Validator::extend(...);
Validator::replacer('foo', function($message, $attribute, $rule, $parameters) {
return str_replace(...);
});
}
~~~
服务 ―― 测试
最后更新于:2022-04-01 04:11:41
# 服务 —— 测试
## 1、简介
Laravel植根于测试,实际上,内置使用[PHPUnit](https://phpunit.de/)对测试提供支持是即开即用的,并且`phpunit.xml`文件已经为应用设置好了。框架还提供了方便的帮助方法允许你对应用进行富有表现力的测试。
`tests`目录中提供了一个`ExampleTest.php`文件,安装完新的Laravel应用后,只需简单在命令行运行`phpunit`来运行测试。
### 1.1 测试环境
运行测试的时候,Laravel自动设置配置环境为`testing`。Laravel在测试时自动配置session和cache驱动为数组驱动,这意味着测试时不会持久化存储session和cache。
如果需要的话你可以自由创建其它测试环境配置。`testing`环境变量可以在`phpunit.xml`文件中配置。
### 1.2 定义&运行测试
要创建一个测试用例,只需简单在`tests`目录创建一个新的测试文件,测试类应该继承`TestCase`,然后你可以使用PHPUnit定义测试方法。要运行测试,简单从终端执行`phpunit`命令即可:
~~~
<?php
class FooTest extends TestCase{
public function testSomethingIsTrue()
{
$this->assertTrue(true);
}
}
~~~
> 注意:如果你在测试类中定义自己的`setUp`方法,确保在其中调用`parent::setUp`。
## 2、应用测试
Laravel为生成HTTP请求、测试输出、以及填充表单提供了平滑的API。举个例子,我们看下`tests`目录下包含的`ExampleTest.php`文件:
~~~
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class ExampleTest extends TestCase{
/**
* 基本功能测试示例
*
* @return void
*/
public function testBasicExample()
{
$this->visit('/')
->see('Laravel 5')
->dontSee('Rails');
}
}
~~~
`visit`方法生成了一个GET请求,`see`方法对我们从应用返回响应中应该看到的给定文本进行断言。`dontSee`方法对给定文本没有从应用响应中返回进行断言。在Laravel中这是最基本的有效应用测试。
### 2.1 与应用交互
当然,除了对响应文本进行断言之外还有做更多测试,让我们看一些点击链接和填充表单的例子:
### 2.1.1 点击链接
在本测试中,我们将为应用生成请求,在返回的响应中“点击”链接,然后对访问URI进行断言。例如,假定响应中有一个“关于我们”的链接:
~~~
<a href="/about-us">About Us</a>
~~~
现在,让我们编写一个测试点击链接并断言用户访问页面是否正确:
~~~
public function testBasicExample(){
$this->visit('/')
->click('About Us')
->seePageIs('/about-us');
}
~~~
### 2.1.2 处理表单
Laravel还为处理表单提供了多个方法。`type`, `select`, `check`, `attach`, 和`press`方法允许你与所有表单输入进行交互。例如,我们假设这个表单存在于应用注册页面:
~~~
<form action="/register" method="POST">
{!! csrf_field() !!}
<div>
Name: <input type="text" name="name">
</div>
<div>
<input type="checkbox" value="yes" name="terms"> Accept Terms
</div>
<div>
<input type="submit" value="Register">
</div>
</form>
~~~
我们可以编写测试完成表单并检查结果:
~~~
public function testNewUserRegistration(){
$this->visit('/register')
->type('Taylor', 'name')
->check('terms')
->press('Register')
->seePageIs('/dashboard');
}
~~~
当然,如果你的表单包含其他输入比如单选按钮或下拉列表,也可以轻松填写这些字段类型。这里是所有表单操作方法列表:
| 方法 | 描述 |
| --- | --- |
| `$this->type($text, $elementName)` | “Type” 文本到给定字段 |
| `$this->select($value, $elementName)` | “Select” 单选框或下拉列表 |
| `$this->check($elementName)` | “Check” 复选框 |
| `$this->attach($pathToFile, $elementName)` | “Attach” 文件到表单 |
| `$this->press($buttonTextOrElementName)` | “Press” 给定文本或name的按钮 |
### 2.1.3 处理附件
如果表单包含`file`输入类型,可以使用`attach`方法添加文件到表单:
~~~
public function testPhotoCanBeUploaded(){
$this->visit('/upload')
->name('File Name', 'name')
->attach($absolutePathToFile, 'photo')
->press('Upload')
->see('Upload Successful!');
}
~~~
### 2.2 测试JSON API
Laravel还提供多个帮助函数用于测试JSON API及其响应。例如,`get`, `post`, `put`, `patch`, 和 `delete`方法用于通过多种HTTP请求方式发出请求。你还可以轻松传递数据和头到这些方法。作为开始,我们编写测试来生成POST请求到`/user`并断言返回的数据是否是JSON格式:
~~~
<?php
class ExampleTest extends TestCase{
/**
* 基本功能测试示例
*
* @return void
*/
public function testBasicExample()
{
$this->post('/user', ['name' => 'Sally'])
->seeJson([
'created' => true,
]);
}
}
~~~
`seeJson`方法将给定数组转化为JSON,然后验证应用返回的整个JSON响应中的JSON片段。因此,如果在JSON响应中有其他属性,只要给定片段存在的话测试依然会通过。
### 2.2.1 精确验证JSON匹配
如果你想要验证给定数组和应用返回的JSON能够精确匹配,使用`seeJsonEquals`方法:
~~~
<?php
class ExampleTest extends TestCase{
/**
* 基本功能测试示例
*
* @return void
*/
public function testBasicExample()
{
$this->post('/user', ['name' => 'Sally'])
->seeJsonEquals([
'created' => true,
]);
}
}
~~~
### 2.3 Session/认证
Laravel提供个多个帮助函数在测试期间处理[session](http://laravelacademy.org/post/230.html),首先,可以使用`withSession`方法设置session值到给定数组。这通常在测试请求前获取session数据时很有用:
~~~
<?php
class ExampleTest extends TestCase{
public function testApplication()
{
$this->withSession(['foo' => 'bar'])
->visit('/');
}
}
~~~
当然,session的通常用于操作用户状态,例如认证用户。帮助函数`actingAs` 提供了认证给定用户为当前用户的简单方法,例如,我们使用[模型工厂](http://laravelacademy.org/post/238.html#model-factories)生成和认证用户:
~~~
<?php
class ExampleTest extends TestCase{
public function testApplication()
{
$user = factory('App\User')->create();
$this->actingAs($user)
->withSession(['foo' => 'bar'])
->visit('/')
->see('Hello, '.$user->name);
}
}
~~~
### 2.4 禁止中间件
测试应用时,为某些测试禁止[中间件](http://laravelacademy.org/post/57.html)很方便。这种机制允许你将路由和控制器与中间件孤立开来做测试,Laravel包含了一个简单的`WithoutMiddleware` trait,可以使用该trait自动在测试类中禁止所有中间件:
~~~
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class ExampleTest extends TestCase{
use WithoutMiddleware;
//
}
~~~
如果你只想在某些方法中禁止中间件,可以在测试方法中调用`withoutMiddleware`方法:
~~~
<?php
class ExampleTest extends TestCase{
/**
* 基本功能测试示例
*
* @return void
*/
public function testBasicExample()
{
$this->withoutMiddleware();
$this->visit('/')
->see('Laravel 5');
}
}
~~~
### 2.5 自定义HTTP请求
如果你想要在应用中生成自定义HTTP请求并获取完整的`Illuminate\Http\Response`对象,可以使用`call`方法:
~~~
public function testApplication(){
$response = $this->call('GET', '/');
$this->assertEquals(200, $response->status());
}
~~~
如果你要生成`POST`, `PUT`, 或者 `PATCH`请求可以在请求中传入输入数据数组,在路由或控制器中可以通过[Request实例](http://laravelacademy.org/post/68.html)访问请求数据:
~~~
$response = $this->call('POST', '/user', ['name' => 'Taylor']);
~~~
## 3、处理数据库
Laravel还提供了多种有用的工具让测试[数据库](http://laravelacademy.org/post/124.html)驱动的应用更加简单。首先,你可以使用帮助函数`seeInDatabase`来断言数据库中的数据是否和给定数据集合匹配。例如,如果你想要通过email值为`sally@example.com`的条件去数据表`users`查询是否存在该记录 ,我们可以这样做:
~~~
public function testDatabase(){
// 调用应用...
$this->seeInDatabase('users', ['email' => 'sally@foo.com']);
}
~~~
### 3.1 每次测试后重置数据库
每次测试后重置数据库通常很有用,这样的话上次测试的数据不会影响下一次测试。
### 3.1.1 使用迁移
一种方式是每次测试后回滚数据库并在下次测试前重新迁移。Laravel提供了一个简单的`DatabaseMigrations` trait来自动为你处理。在测试类上简单使用该trait如下:
~~~
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class ExampleTest extends TestCase{
use DatabaseMigrations;
/**
* 基本功能测试示例
*
* @return void
*/
public function testBasicExample()
{
$this->visit('/')
->see('Laravel 5');
}
}
~~~
### 3.1.2 使用事务
另一种方式是将每一个测试用例包裹到一个数据库事务中,Laravel提供了方便的 `DatabaseTransactions` trait自动为你处理:
~~~
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class ExampleTest extends TestCase{
use DatabaseTransactions;
/**
* 基本功能测试示例
*
* @return void
*/
public function testBasicExample()
{
$this->visit('/')
->see('Laravel 5');
}
}
~~~
### 3.2 模型工厂
测试时,通常需要在执行测试前插入新数据到数据库。在创建测试数据时,Laravel允许你使用”factories”为每个Eloquent模型定义默认的属性值集合,而不用手动为每一列指定值。作为开始,我们看一下`database/factories/ModelFactory.php`文件,该文件包含了一个工厂定义:
~~~
$factory->define(App\User::class, function (Faker\Generator $faker) {
return [
'name' => $faker->name,
'email' => $faker->email,
'password' => bcrypt(str_random(10)),
'remember_token' => str_random(10),
];
});
~~~
在闭包中,作为工厂定义,我们返回该模型上所有属性默认测试值。该闭包接收PHP库[Faker](https://github.com/fzaninotto/Faker)实例,从而允许你方便地为测试生成多种类型的随机数据。
当然,你可以添加更多工厂到`ModelFactory.php`文件。
### 3.2.1 多个工厂类型
有时候你可能想要为同一个Eloquent模型类生成多个工厂,例如,除了正常用户外可能你想要为“管理员”用户生成一个工厂,你可以使用`defineAs`方法定义这些工厂:
~~~
$factory->defineAs(App\User::class, 'admin', function ($faker) {
return [
'name' => $faker->name,
'email' => $faker->email,
'password' => str_random(10),
'remember_token' => str_random(10),
'admin' => true,
];
});
~~~
你可以使用`raw`方法获取基本属性而不用重复基本用户工厂中的所有属性,获取这些属性后,只需将你要求的额外值增补进去即可:
~~~
$factory->defineAs(App\User::class, 'admin', function ($faker) use ($factory) {
$user = $factory->raw(App\User::class);
return array_merge($user, ['admin' => true]);
});
~~~
### 3.2.2 在测试中使用工厂
定义好工厂后,可以在测试或数据库填充文件中通过全局的`factory`方法使用它们来生成模型实例,所以,让我们看一些生成模型的例子,首先,我们使用`make`方法,该方法创建模型但不将其保存到数据库:
~~~
public function testDatabase(){
$user = factory(App\User::class)->make();
// 用户模型测试...
}
~~~
如果你想要覆盖模型的一些默认值,可以传递数组值到`make`方法。只有指定值被替换,其他数据保持不变:
~~~
$user = factory(App\User::class)->make([
'name' => 'Abigail',
]);
~~~
还可以创建多个模型集合或者创建给定类型的集合:
~~~
// 创建3个 App\User 实例...
$users = factory(App\User::class, 3)->make();
// 创建1个 App\User "admin" 实例...
$user = factory(App\User::class, 'admin')->make();
// 创建3个 App\User "admin" 实例...
$users = factory(App\User::class, 'admin', 3)->make();
~~~
### 3.2.3 持久化工厂模型
`create`方法不仅能创建模型实例,还可以使用Eloquent的`save`方法将它们保存到数据库:
~~~
public function testDatabase(){
$user = factory(App\User::class)->create();
//用户模型测试...
}
~~~
你仍然可以通过传递数组到create方法覆盖模型上的属性:
~~~
$user = factory(App\User::class)->create([
'name' => 'Abigail',
]);
~~~
### 3.2.4 添加关联关系到模型
你甚至可以持久化多个模型到数据库。在本例中,我们添加一个关联到创建的模型,使用`create`方法创建多个模型的时候,会返回一个[Eloquent集合](http://laravelacademy.org/post/144.html)实例,从而允许你使用集合提供的所有便利方法,例如`each`:
~~~
$users = factory(App\User::class, 3)
->create()
->each(function($u) {
$u->posts()->save(factory(App\Post::class)->make());
});
~~~
## 4、模拟
### 4.1 模拟事件
如果你在重度使用Laravel的时间系统,可能想要在测试时模拟特定事件。例如,如果你在测试用户注册,你可能不想所有`UserRegistered`的时间处理器都被触发,因为这可能会发送欢迎邮件,等等。
Laravel提供可一个方便的`expectsEvents`方法来验证期望的事件被触发,但同时阻止该事件的其它处理器运行:
~~~
<?php
class ExampleTest extends TestCase{
public function testUserRegistration()
{
$this->expectsEvents(App\Events\UserRegistered::class);
// 测试用户注册代码...
}
}
~~~
如果你想要阻止所有事件运行,可以使用`withoutEvents`方法:
~~~
<?php
class ExampleTest extends TestCase{
public function testUserRegistration()
{
$this->withoutEvents();
// 测试用户注册代码...
}
}
~~~
### 4.2 模拟[队列任务](http://laravelacademy.org/post/222.html)
有时候,你可能想要在请求时简单测试控制器分发的指定任务,这允许你孤立的测试路由/控制器——将其从任务逻辑中分离出去,当然,接下来你可以在一个独立测试类中测试任务本身。
Laravel提供了一个方便的`expectsJobs`方法来验证期望的任务被分发,但该任务本身不会被测试:
~~~
<?php
class ExampleTest extends TestCase{
public function testPurchasePodcast()
{
$this->expectsJobs(App\Jobs\PurchasePodcast::class);
// 测试购买播客代码...
}
}
~~~
> 注意:这个方法只检查通过`DispatchesJobs` trait分发方法分发的任务,并不检查直接通过`Queue::push`分发的任务。
### 4.3 模拟门面
测试的时候,你可能经常想要模拟[Laravel门面](http://laravelacademy.org/post/97.html)的调用,例如,看看下面的控制器动作:
~~~
<?php
namespace App\Http\Controllers;
use Cache;
use Illuminate\Routing\Controller;
class UserController extends Controller{
/**
* 显示应用用户列表
*
* @return Response
*/
public function index()
{
$value = Cache::get('key');
//
}
}
~~~
我们可以通过使用`shouldReceive`方法模拟`Cache`门面的调用,该方法返回一个[Mockery](https://github.com/padraic/mockery)模拟的实例,由于门面通过Laravel[服务容器](http://laravelacademy.org/post/93.html)解析和管理,它们比通常的静态类更具有可测试性。例如,我们来模拟`Cache`门面的调用:
~~~
<?php
class FooTest extends TestCase{
public function testGetIndex()
{
Cache::shouldReceive('get')
->once()
->with('key')
->andReturn('value');
$this->visit('/users')->see('value');
}
}
~~~
> 注意:不要模拟`Request`门面,取而代之地,在测试时传递输入到HTTP帮助函数如`call`和`post`。
服务 ―― 任务调度
最后更新于:2022-04-01 04:11:39
# 服务 —— 任务调度
## 1、简介
在以前,开发者需要为每一个需要调度然后添加这些Cron条目。Laravel命令调度器允许你平滑而又富有表现力地在Laravel中定义命令调度,并且服务器上只需要一个Cron条目即可。
任务调度定义在`app/Console/Kernel.php`文件的`schedule`方法中,该方法中已经包含了一个示例。你可以自由地添加你需要的调度任务到`Schedule`对象。
### 1.1 开启调度
下面是你唯一需要添加到服务器的Cron条目:
~~~
* * * * * php /path/to/artisan schedule:run 1>> /dev/null 2>&1
~~~
该Cron将会每分钟调用Laravel命令调度,然后,Laravel评估你的调度任务并运行到期的任务。
## 2、定义调度
你可以在`App\Console\Kernel`类的`schedule`方法中定义所有调度任务。开始之前,让我们看一个调度任务的例子,在这个例子中,我们将会在每天午夜调度一个被调用的闭包。在这个闭包中我们将会执行一个数据库查询来清空表:
~~~
<?php
namespace App\Console;
use DB;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel{
/**
* 应用提供的Artisan命令
*
* @var array
*/
protected $commands = [
'App\Console\Commands\Inspire',
];
/**
* 定义应用的命令调度
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
*/
protected function schedule(Schedule $schedule)
{
$schedule->call(function () {
DB::table('recent_users')->delete();
})->daily();
}
}
~~~
除了调度闭包调用外,还可以调度[Artisan命令](http://laravelacademy.org/post/170.html)和操作系统命令。例如,可以使用`command`方法来调度一个Artisan命令:
~~~
$schedule->command('emails:send --force')->daily();
~~~
`exec`命令可用于发送命令到操作系统:
~~~
$schedule->exec('node /home/forge/script.js')->daily();
~~~
### 2.1 调度常用选项
当然,你可以分配多种调度到任务:
| 方法 | 描述 |
| --- | --- |
| `->cron('* * * * *');` | 在自定义Cron调度上运行任务 |
| `->everyMinute();` | 每分钟运行一次任务 |
| `->everyFiveMinutes();` | 每五分钟运行一次任务 |
| `->everyTenMinutes();` | 每十分钟运行一次任务 |
| `->everyThirtyMinutes();` | 每三十分钟运行一次任务 |
| `->hourly();` | 每小时运行一次任务 |
| `->daily();` | 每天凌晨零点运行任务 |
| `->dailyAt('13:00');` | 每天13:00运行任务 |
| `->twiceDaily(1, 13);` | 每天1:00 & 13:00运行任务 |
| `->weekly();` | 每周运行一次任务 |
| `->monthly();` | 每月运行一次任务 |
这些方法可以和额外的约束一起联合起来创建一周特定时间运行的更加细粒度的调度,例如,要每周一调度一个命令:
~~~
$schedule->call(function () {
// 每周星期一13:00运行一次...
})->weekly()->mondays()->at('13:00');
~~~
下面是额外的调度约束列表:
| 方法 | 描述 |
| --- | --- |
| `->weekdays();` | 只在工作日运行任务 |
| `->sundays();` | 每个星期天运行任务 |
| `->mondays();` | 每个星期一运行任务 |
| `->tuesdays();` | 每个星期二运行任务 |
| `->wednesdays();` | 每个星期三运行任务 |
| `->thursdays();` | 每个星期四运行任务 |
| `->fridays();` | 每个星期五运行任务 |
| `->saturdays();` | 每个星期六运行任务 |
| `->when(Closure);` | 基于特定测试运行任务 |
### 2.1.1 基于测试的约束条件
`when`方法用于限制任务在通过给定测试之后运行。换句话说,如果给定闭包返回`true`,只要没有其它约束条件阻止任务运行,该任务就会执行:
~~~
$schedule->command('emails:send')->daily()->when(function () {
return true;
});
~~~
### 2.2 避免任务重叠
默认情况下,即使前一个任务仍然在运行调度任务也会运行,要避免这样的情况,可使用`withoutOverlapping`方法:
~~~
$schedule->command('emails:send')->withoutOverlapping();
~~~
在本例中,Artisan命令`emails:send`每分钟都会运行,如果该命令没有在运行的话。如果你的任务在执行时经常大幅度的变化,那么`withoutOverlapping`方法就非常有用,你不必再去预测给定任务到底要消耗多长时间。
## 3、任务输出
Laravel调度器为处理调度任务输出提供了多个方便的方法。首先,使用`sendOutputTo`方法,你可以发送输出到文件以便稍后检查:
~~~
$schedule->command('emails:send')
->daily()
->sendOutputTo($filePath);
~~~
使用`emailOutputTo`方法,你可以将输出发送到电子邮件,注意输出必须首先通过`sendOutputTo`方法发送到文件。还有,使用电子邮件发送任务输出之前,应该配置Laravel的[电子邮件服务](http://laravelacademy.org/post/213.html):
~~~
$schedule->command('foo')
->daily()
->sendOutputTo($filePath)
->emailOutputTo('foo@example.com');
~~~
> 注意:`emailOutputTo`和`sendOutputTo`方法只对`command`方法有效,不支持`call`方法。
## 4、任务钩子
使用`before`和`after`方法,你可以指定在调度任务完成之前和之后要执行的代码:
~~~
$schedule->command('emails:send')
->daily()
->before(function () {
// Task is about to start...
})
->after(function () {
// Task is complete...
});
~~~
### 4.1 ping URL
使用`pingBefore`和`thenPing`方法,调度器可以在任务完成之前和之后自动ping给定的URL。该方法在通知外部服务时很有用,例如[Laravel Envoyer](https://envoyer.io/),在调度任务开始或完成的时候:
~~~
$schedule->command('emails:send')
->daily()
->pingBefore($url)
->thenPing($url);
~~~
使用`pingBefore($url)`或`thenPing($url)`特性需要安装HTTP库Guzzle,可以在`composer.json` 文件中添加如下行来安装Guzzle到项目:
~~~
"guzzlehttp/guzzle": "~5.3|~6.0"
~~~
服务 ―― Envoy 任务运行器(SSH任务)
最后更新于:2022-04-01 04:11:36
# 服务 —— Envoy 任务运行器(SSH任务)
## 1、简介
[Laravel Envoy](https://github.com/laravel/envoy) 为定义运行在远程主机上的通用任务只支持Mac和Linux操作系统。
### 1.1 安装
首先,使用Composer的`global` 命令安装Envoy:
~~~
composer global require "laravel/envoy=~1.0"
~~~
确保`~/.composer/vendor/bin`目录在系统路径PATH中否则在终端中由于找不到`envoy`而无法执行该命令。
### 1.1.1 更新Envoy
还可以使用Composer保持安装的Envoy是最新版本:
~~~
composer global update
~~~
## 2、编写任务
所有的Envoy任务都定义在项目根目录下的`Envoy.blade.php`文件中,下面是一个让你开始的示例:
~~~
@servers(['web' => 'user@192.168.1.1'])
@task('foo', ['on' => 'web'])
ls -la
@endtask
~~~
正如你所看到的,`@servers`数组定义在文件顶部,从而允许你在任务声明中使用`on`选项引用这些服务器,在 `@task`声明中,应该放置将要在服务器上运行的Bash代码。
**启动**
有时候,你需要在评估Envoy任务之前执行一些PHP代码,可以在Envoy文件中使用`@setup`指令来声明变量和要执行的PHP代码:
~~~
@setup
$now = new DateTime();
$environment = isset($env) ? $env : "testing";
@endsetup
~~~
还可以使用`@include`来引入外部PHP文件:
~~~
@include('vendor/autoload.php');
~~~
**确认任务**
如果你想要在服务器上运行给定任务之前弹出弹出提示进行确认,可以在任务声明中使用`confirm`指令:
~~~
@task('deploy', ['on' => 'web', 'confirm' => true])
cd site
git pull origin {{ $branch }}
php artisan migrate
@endtask
~~~
### 2.1 任务变量
如果需要的话,你可以使用命令行开关传递变量到Envoy文件,从而允许你自定义任务:
~~~
envoy run deploy --branch=master
~~~
你可以在任务中通过Blade的“echo”语法使用该选项:
~~~
@servers(['web' => '192.168.1.1'])
@task('deploy', ['on' => 'web'])
cd site
git pull origin {{ $branch }}
php artisan migrate
@endtask
~~~
### 2.2 多个服务器
你可以轻松地在多主机上运行同一个任务,首先,添加额外服务器到`@servers`声明,每个服务器应该被指配一个唯一的名字。定义好服务器后,在任务声明中简单列出所有服务器即可:
~~~
@servers(['web-1' => '192.168.1.1', 'web-2' => '192.168.1.2'])
@task('deploy', ['on' => ['web-1', 'web-2']])
cd site
git pull origin {{ $branch }}
php artisan migrate
@endtask
~~~
默认情况下,该任务将会依次在每个服务器上执行,这意味着,该任务在第一台服务器上运行完成后才会开始在第二台服务器运行。
### 2.2.1 平行运行
如果你想要在多个服务器上平行运行,添加`parallel`选项到任务声明:
~~~
@servers(['web-1' => '192.168.1.1', 'web-2' => '192.168.1.2'])
@task('deploy', ['on' => ['web-1', 'web-2'], 'parallel' => true])
cd site
git pull origin {{ $branch }}
php artisan migrate
@endtask
~~~
### 2.3 任务宏
宏允许你使用单个命令中定义多个依次运行的任务。例如,`deploy`宏会运行git和composer任务:
~~~
@servers(['web' => '192.168.1.1'])
@macro('deploy')
git
composer
@endmacro
@task('git')
git pull origin master
@endtask
@task('composer')
composer install
@endtask
~~~
宏被定义好了之后,你就可以通过如下单个命令运行它:
~~~
envoy run deploy
~~~
## 3、运行任务
要从`Envoy.blade.php`文件中运行一个任务,需要执行Envoy的`run`命令,然后传递你要执行的任务的命令名或宏。Envoy将会运行命令并从服务打印输出:
~~~
envoy run task
~~~
## 4、通知
### 4.1 HipChat
运行完一个任务后,可以使用Envoy的`@hipchat`指令发送通知到团队的[HipChat](https://www.hipchat.com/)房间,该指令接收一个API令牌、房间名称、和用户名:
~~~
@servers(['web' => '192.168.1.1'])
@task('foo', ['on' => 'web'])
ls -la
@endtask
@after
@hipchat('token', 'room', 'Envoy')
@endafter
~~~
需要的话,你还可以传递自定义发送给HipChat房间的消息,所有在Envoy任务中有效的变量在构建消息时也有效:
~~~
@after
@hipchat('token', 'room', 'Envoy', "{$task} ran in the {$env} environment.")
@endafter
~~~
### 4.2 Slack
除了HipChat之外,Envoy还支持发送通知到[Slack](https://slack.com/)。`@slack`指令接收一个Slack钩子URL、频道名称、和你要发送给该频道的消息:
~~~
@after
@slack('hook', 'channel', 'message')
@endafter
~~~
你可以通过创建集成到Slack网站的Incoming WebHooks来获取钩子URL,该hook参数是由 Incoming Webhooks Slack 集成提供的完整webhook URL,例如:
~~~
https://hooks.slack.com/services/ZZZZZZZZZ/YYYYYYYYY/XXXXXXXXXXXXXXX
~~~
你可以提供下面两种其中之一作为频道参数:
* 发送消息到频道: `#channel`
* 发送消息到用户: `@user`
服务 ―― Session
最后更新于:2022-04-01 04:11:34
# 服务 —— Session
## 1、简介
由于HTTP。
### 1.1 配置
Session配置文件位于`config/session.php`。默认情况下,Laravel使用的`session`驱动为文件驱动,这对许多应用而言是没有什么问题的。在生产环境中,你可能考虑使用`memcached`或者`redis`驱动以便获取更快的session性能。
`session`驱动定义请求的session数据存放在哪里,Laravel可以处理多种类型的驱动:
* `file` – session数据存储在 `storage/framework/sessions`目录下;
* `cookie` – session数据存储在经过加密的安全的cookie中;
* `database` – session数据存储在数据库中
* `memcached` / `redis` – session数据存储在memcached/redis中;
* `array` – session数据存储在简单PHP数组中,在多个请求之间是非持久化的。
> 注意:数组驱动通常用于运行测试以避免session数据持久化。
### 1.2 Session驱动预备知识
### 1.2.1 数据库
当使用`database`session驱动时,需要设置表包含`session`项,下面是该数据表的表结构声明:
~~~
Schema::create('sessions', function ($table) {
$table->string('id')->unique();
$table->text('payload');
$table->integer('last_activity');
});
~~~
你可以使用Artisan命令`session:table`来生成迁移:
~~~
php artisan session:table
composer dump-autoload
php artisan migrate
~~~
### 1.2.2 Redis
在Laravel中使用Redis `session`驱动前,需要通过Composer安装`predis/predis`包。
### 1.3 其它Session相关问题
Laravel框架内部使用`flash` session键,所以你不应该通过该名称添加数据项到session。
如果你需要所有存储的session数据经过加密,在配置文件中设置`encrypt`配置为`true`。
## 2、基本使用
**访问session**
首先,我们来访问session,我们可以通过HTTP请求访问session实例,可以在控制器方法中通过类型提示引入请求实例,记住,控制器方法依赖通过Laravel[服务容器](http://laravelacademy.org/post/93.html)注入:
~~~
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class UserController extends Controller{
/**
* 显示指定用户的属性
*
* @param Request $request
* @param int $id
* @return Response
*/
public function showProfile(Request $request, $id)
{
$value = $request->session()->get('key');
//
}
}
~~~
从session中获取数据的时候,还可以传递默认值作为第二个参数到`get`方法,默认值在指定键在session中不存在时返回。如果你传递一个闭包作为默认值到`get`方法,该闭包会执行并返回执行结果:
~~~
$value = $request->session()->get('key', 'default');
$value = $request->session()->get('key', function() {
return 'default';
});
~~~
如果你想要从session中获取所有数据,可以使用`all`方法:
~~~
$data = $request->session()->all();
~~~
还可以使用全局的PHP函数`session`来获取和存储session中的数据:
~~~
Route::get('home', function () {
// 从session中获取数据...
$value = session('key');
// 存储数据到session...
session(['key' => 'value']);
});
~~~
**判断session中是否存在指定项**
`has`方法可用于检查数据项在session中是否存在。如果存在的话返回`true`:
~~~
if ($request->session()->has('users')) {
//
}
~~~
**在session中存储数据**
获取到session实例后,就可以调用多个方法来与底层数据进行交互,例如,`put`方法存储新的数据到session中:
~~~
$request->session()->put('key', 'value');
~~~
**推送数据到数组session**
`push` 方法可用于推送数据到值为数组的session,例如,如果`user.teams`键包含团队名数组,可以像这样推送新值到该数组:
~~~
$request->session()->push('user.teams', 'developers');
~~~
**获取并删除数据**
`pull`方法将会从session获取并删除数据:
~~~
$value = $request->session()->pull('key', 'default');
~~~
**从session中删除数据项**
`forget`方法从session中移除指定数据,如果你想要从session中移除所有数据,可以使用`flush`方法:
~~~
$request->session()->forget('key');
$request->session()->flush();
~~~
**重新生成Session ID**
如果你需要重新生成session ID,可以使用`regenerate`方法:
~~~
$request->session()->regenerate();
~~~
### 2.1 一次性数据
有时候你可能想要在session中存储只在下个请求中有效的数据,可以通过`flash`方法来实现。使用该方法存储的session数据只在随后的HTTP请求中有效,然后将会被删除:
~~~
$request->session()->flash('status', 'Task was successful!');
~~~
如果你需要在更多请求中保持该一次性数据,可以使用`reflash`方法,该方法将所有一次性数据保留到下一个请求,如果你只是想要保存特定一次性数据,可以使用`keep`方法:
~~~
$request->session()->reflash();
$request->session()->keep(['username', 'email']);
~~~
## 3、添加自定义Session驱动
要为Laravel后端session添加更多驱动,可以使用Session门面上的`extend`方法。可以在[服务提供者](http://laravelacademy.org/post/91.html)的`boot`方法中调用该方法:
~~~
<?php
namespace App\Providers;
use Session;
use App\Extensions\MongoSessionStore;
use Illuminate\Support\ServiceProvider;
class SessionServiceProvider extends ServiceProvider{
/**
* Perform post-registration booting of services.
*
* @return void
*/
public function boot()
{
Session::extend('mongo', function($app) {
// Return implementation of SessionHandlerInterface...
return new MongoSessionStore;
});
}
/**
* Register bindings in the container.
*
* @return void
*/
public function register()
{
//
}
}
~~~
需要注意的是自定义session驱动需要实现`SessionHandlerInterface`接口,该接口包含少许我们需要实现的方法,一个MongoDB的实现如下:
~~~
<?php
namespace App\Extensions;
class MongoHandler implements SessionHandlerInterface{
public function open($savePath, $sessionName) {}
public function close() {}
public function read($sessionId) {}
public function write($sessionId, $data) {}
public function destroy($sessionId) {}
public function gc($lifetime) {}
}
~~~
由于这些方法并不像缓存的`StoreInterface`接口方法那样容易理解,我们接下来快速过一遍每一个方法:
* `open` 方法用于基于文件的session存储系统,由于Laravel已经有了一个 `file` session 驱动,所以在该方法中不需要放置任何代码,可以将其置为空方法。
* `close` 方法和`open` 方法一样,也可以被忽略,对大多数驱动而言都用不到该方法。
* `read` 方法应该返回与给定$sessionId 相匹配的session数据的字符串版本,从驱动中获取或存储session数据不需要做任何序列化或其它编码,因为Laravel已经为我们做了序列化。
* `write` 方法应该讲给定`$data` 写到持久化存储系统相应的`$sessionId` , 例如MongoDB, Dynamo等等。
* `destroy` 方法从持久化存储中移除 `$sessionId` 对应的数据。
* `gc` 方法销毁大于给定$lifetime 的所有 session数据,对本身拥有过期机制的系统如 Memcached 和Redis而言,该方法可以留空。
session驱动被注册之后,就可以在配置文件`config/session.php`中使用`mongo` 驱动了。
服务 ―― Redis
最后更新于:2022-04-01 04:11:32
# 服务 —— Redis
## 1、简介
[Redis](http://redis.io/)是一个开源的、高级的键值对存储系统,经常被用作数据结构服务器,因为其支持[字符串](http://redis.io/topics/data-types#strings)、[Hash](http://redis.io/topics/data-types#hashes)、[列表](http://redis.io/topics/data-types#lists)、[集合](http://redis.io/topics/data-types#sets)和[有序集合](http://redis.io/topics/data-types#sorted-sets)等数据结构。在Laravel中使用Redis/predis`包(~1.0)。
### 1.1 配置
应用的Redis配置位于配置文件`config/database.php`。在这个文件中,可以看到包含被应用使用的Redis服务器的`redis`数组:
~~~
'redis' => [
'cluster' => false,
'default' => [
'host' => '127.0.0.1',
'port' => 6379,
'database' => 0,
],
],
~~~
默认服务器配置可以满足开发需要,然而,你可以基于环境随意修改该数组,只需要给每个Redis服务器一个名字并指定该Redis服务器使用的主机和接口。
`cluster`选项告诉Laravel Redis 客户端在多个Redis节点间执行客户端分片,从而形成节点池并创建大量有效的RAM。然而,客户端分片并不处理故障转移,所以,非常适合从另一个主数据存储那里获取有效的缓存数据。
此外,你可以在Redis连接定义中定义options数组值,从而允许你指定一系列Predis[客户端选项](https://github.com/nrk/predis/wiki/Client-Options)。
如果Redis服务器要求认证信息,你可以通过添加`password`配置项到Redis服务器配置数组来提供密码。
> 注意:如果你通过PECL安装PHP的Redis扩展,需要在 `config/app.php` 文件中修改Redis的别名。
## 2、基本使用
你可以通过调用`Redis`[门面](http://laravelacademy.org/post/97.html)上的多个方法来与Redis进行交互,该门面支持动态方法,所以你可以任何[Redis命令](http://redis.io/commands),该命令将会直接传递给Redis,在本例中,我们通过调用`Redis`门面上的`get`方法来调用Redis上的GET命令:
~~~
<?php
namespace App\Http\Controllers;
use Redis;use App\Http\Controllers\Controller;
class UserController extends Controller{
/**
* 显示指定用户属性
*
* @param int $id
* @return Response
*/
public function showProfile($id)
{
$user = Redis::get('user:profile:'.$id);
return view('user.profile', ['user' => $user]);
}
}
~~~
当然,如上所述,可以在`Redis`门面上调用任何Redis命令。Laravel使用魔术方法将命令传递给Redis服务器,所以只需简单传递参数和Redis命令如下:
~~~
Redis::set('name', 'Taylor');
$values = Redis::lrange('names', 5, 10);
~~~
此外还可以使用`command`方法传递命令到服务器,该方法接收命令名作为第一个参数,参数值数组作为第二个参数:
~~~
$values = Redis::command('lrange', ['name', 5, 10]);
~~~
**使用多个Redis连接
你可以通过调用`Redis::connection`方法获取Redis实例:
~~~
$redis = Redis::connection();
~~~
这将会获取默认Redis服务器实例,如果你没有使用服务器集群,可以传递服务器名到`connection`方法来获取指定Redis配置中定义的指定服务器:
~~~
$redis = Redis::connection('other');
~~~
### 2.1 管道命令
当你需要在一次操作中发送多个命令到服务器的时候应该使用管道,`pipeline`方法接收一个参数:接收Redis实例的闭包。你可以将所有Redis命令发送到这个Redis实例,然后这些命令会在一次操作中被执行:
~~~
Redis::pipeline(function ($pipe) {
for ($i = 0; $i < 1000; $i++) {
$pipe->set("key:$i", $i);
}
});
~~~
## 3、发布
Redis还提供了调用Redis的`publish`和`subscribe`命令的接口。这些Redis命令允许你在给定“频道”监听消息,你可以从另外一个应用发布消息到这个频道,甚至使用其它编程语言,从而允许你在不同的应用/进程之间轻松通信。
首先,让我们使用`subscribe`方法通过Redis在一个频道上设置监听器。由于调用`subscribe`方法会开启一个常驻进程,我们将在[Artisan命令](http://laravelacademy.org/post/170.html)中调用该方法:
~~~
<?php
namespace App\Console\Commands;
use Redis;
use Illuminate\Console\Command;
class RedisSubscribe extends Command{
/**
* 控制台命令名称
*
* @var string
*/
protected $signature = 'redis:subscribe';
/**
* 控制台命令描述
*
* @var string
*/
protected $description = 'Subscribe to a Redis channel';
/**
* 执行控制台命令
*
* @return mixed
*/
public function handle()
{
Redis::subscribe(['test-channel'], function($message) {
echo $message;
});
}
}
~~~
现在,我们可以使用`publish`发布消息到该频道:
~~~
Route::get('publish', function () {
// 路由逻辑...
Redis::publish('test-channel', json_encode(['foo' => 'bar']));
});
~~~
### 3.1 通配符订阅
使用`psubscribe`方法,你可以订阅到一个通配符定义的频道,这在所有相应频道上获取所有消息时很有用。`$channel`名将会作为第二个参数传递给提供的回调闭包:
~~~
Redis::psubscribe(['*'], function($message, $channel) {
echo $message;
});
Redis::psubscribe(['users.*'], function($message, $channel) {
echo $message;
});
~~~
服务 ―― 队列
最后更新于:2022-04-01 04:11:30
# 服务 —— 队列
## 1、简介
Laravel队列服务为各种不同的后台队列提供了统一的API。队列允许你推迟耗时任务(例如发送邮件)的执行,从而大幅提高web请求速度。
### 1.1 配置
队列配置文件存放在`config/queue.php`。在该文件中你将会找到框架自带的每一个队列驱动的连接配置,包括数据库、[Beanstalkd](http://kr.github.com/beanstalkd)、 [IronMQ](http://iron.io/)、 [Amazon SQS](http://aws.amazon.com/sqs)、 [Redis](http://redis.io/)以及同步(本地使用)驱动。其中还包含了一个null队列驱动以拒绝队列任务。
### 1.2 队列驱动预备知识
### 1.2.1 数据库
为了使用`database`队列驱动,需要一张数据库表来存放任务,要生成创建该表的迁移,运行Artisan命令`queue:table`,迁移被创建好了之后,使用`migrate`命令运行迁移:
~~~
php artisan queue:table
php artisan migrate
~~~
### 1.2.2 其它队列依赖
下面是以上列出队列驱动需要安装的依赖:
* Amazon SQS: `aws/aws-sdk-php ~3.0`
* Beanstalkd: `pda/pheanstalk ~3.0`
* IronMQ: `iron-io/iron_mq ~2.0`
* Redis: `predis/predis ~1.0`
## 2、编写任务类
### 2.1 生成任务类
默认情况下,应用的所有队列任务都存放在`app/Jobs`目录。你可以使用Artisan CLI生成新的队列任务:
~~~
php artisan make:job SendReminderEmail --queued
~~~
该命令将会在`app/Jobs`目录下生成一个新的类,并且该类实现了`Illuminate\Contracts\Queue\ShouldQueue`接口,告诉Laravel该任务应该被推送到队列而不是同步运行。
### 2.2 任务类结构
任务类非常简单,正常情况下只包含一个当队列处理该任务时被执行的`handle`方法,让我们看一个任务类的例子:
~~~
<?php
namespace App\Jobs;
use App\User;
use App\Jobs\Job;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Bus\SelfHandling;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendReminderEmail extends Job implements SelfHandling, ShouldQueue
{
use InteractsWithQueue, SerializesModels;
protected $user;
/**
* 创建一个新的任务实例
*
* @param User $user
* @return void
*/
public function __construct(User $user)
{
$this->user = $user;
}
/**
* 执行任务
*
* @param Mailer $mailer
* @return void
*/
public function handle(Mailer $mailer)
{
$mailer->send('emails.reminder', ['user' => $this->user], function ($m) {
//
});
$this->user->reminders()->create(...);
}
}
~~~
在本例中,注意我们能够直接将Eloquent模型传递到对列任务的构造函数中。由于该任务使用了`SerializesModels` trait,Eloquent模型将会在任务被执行是优雅地序列化和反序列化。如果你的队列任务在构造函数中接收Eloquent模型,只有模型的主键会被序列化到队列,当任务真正被执行的时候,队列系统会自动从数据库中获取整个模型实例。这对应用而言是完全透明的,从而避免序列化整个Eloquent模型实例引起的问题。
`handle`方法在任务被队列处理的时候被调用,注意我们可以在任务的`handle`方法中对依赖进行类型提示。Laravel[服务容器](http://laravelacademy.org/post/93.html)会自动注入这些依赖。
### 2.2.1 出错
如果任务被处理的时候抛出异常,则该任务将会被自动释放回队列以便再次尝试执行。任务会持续被释放知道尝试次数达到应用允许的最大次数。最大尝试次数通过Artisan任务`queue:listen`或`queue:work`上的`--tries`开关来定义。关于运行队列监听器的更多信息可以在[下面](http://laravelacademy.org/post/222.html#running-the-queue-listener)看到。
### 2.2.2 手动释放任务
如果你想要手动释放任务,生成的任务类中自带的`InteractsWithQueue` trait提供了释放队列任务的`release`方法,该方法接收一个参数——同一个任务两次运行之间的等待时间:
~~~
public function handle(Mailer $mailer){
if (condition) {
$this->release(10);
}
}
~~~
### 2.2.3 检查尝试运行次数
正如上面提到的,如果在任务处理期间发生异常,任务会自动释放回队列中,你可以通过`attempts`方法来检查该任务已经尝试运行次数:
~~~
public function handle(Mailer $mailer){
if ($this->attempts() > 3) {
//
}
}
~~~
## 3、推送任务到队列
默认的Laravel控制器位于`app/Http/Controllers/Controller.php`并使用了`DispatchesJobs` trait。该trait提供了一些允许你方便推送任务到队列的方法,例如`dispatch`方法:
~~~
<?php
namespace App\Http\Controllers;
use App\User;
use Illuminate\Http\Request;
use App\Jobs\SendReminderEmail;
use App\Http\Controllers\Controller;
class UserController extends Controller{
/**
* 发送提醒邮件到指定用户
*
* @param Request $request
* @param int $id
* @return Response
*/
public function sendReminderEmail(Request $request, $id)
{
$user = User::findOrFail($id);
$this->dispatch(new SendReminderEmail($user));
}
}
~~~
当然,有时候你想要从应用中路由或控制器之外的某些地方分发任务,因为这个原因,你可以在应用的任何类中包含`DispatchesJobs` trait,从而获取对分发方法的访问,举个例子,下面是使用该trait的示例类:
~~~
<?php
namespace App;
use Illuminate\Foundation\Bus\DispatchesJobs;
class ExampleClass{
use DispatchesJobs;
}
~~~
**为任务指定队列**
你还可以指定任务被发送到的队列。
通过推送任务到不同队列,你可以对队列任务进行“分类”,甚至优先考虑分配给多个队列的worker数目。这并不会如队列配置文件中定义的那样将任务推送到不同队列“连接”,而只是在单个连接中发送给特定队列。要指定该队列,使用任务实例上的`onQueue`方法,该方法有Laravel自带的基类`App\Jobs\Job`提供:
~~~
<?php
namespace App\Http\Controllers;
use App\User;
use Illuminate\Http\Request;
use App\Jobs\SendReminderEmail;
use App\Http\Controllers\Controller;
class UserController extends Controller{
/**
* 发送提醒邮件到指定用户
*
* @param Request $request
* @param int $id
* @return Response
*/
public function sendReminderEmail(Request $request, $id)
{
$user = User::findOrFail($id);
$job = (new SendReminderEmail($user))->onQueue('emails');
$this->dispatch($job);
}
}
~~~
### 3.1 延迟任务
有时候你可能想要延迟队列任务的执行。例如,你可能想要将一个注册15分钟后给消费者发送提醒邮件的任务放到队列中,可以通过使用任务类上的`delay`方法来实现,该方法由`Illuminate\Bus\Queueable` trait提供:
~~~
<?php
namespace App\Http\Controllers;
use App\User;
use Illuminate\Http\Request;
use App\Jobs\SendReminderEmail;
use App\Http\Controllers\Controller;
class UserController extends Controller{
/**
* 发送提醒邮件到指定用户
*
* @param Request $request
* @param int $id
* @return Response
*/
public function sendReminderEmail(Request $request, $id)
{
$user = User::findOrFail($id);
$job = (new SendReminderEmail($user))->delay(60);
$this->dispatch($job);
}
}
~~~
在本例中,我们指定任务在队列中开始执行前延迟60秒。
> 注意:Amazon SQS服务最大延迟时间是15分钟。
### 3.2 从请求中分发任务
映射HTTP请求变量到任务中很常见,Laravel提供了一些帮助函数让这种实现变得简单,而不用每次请求时手动执行映射。让我么看一下 `DispatchesJobs` trait上的`dispatchFrom`方法。默认情况下,该trait包含在Laravel控制器基类中:
~~~
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class CommerceController extends Controller{
/**
* 处理指定订单
*
* @param Request $request
* @param int $id
* @return Response
*/
public function processOrder(Request $request, $id)
{
// 处理请求...
$this->dispatchFrom('App\Jobs\ProcessOrder', $request);
}
}
~~~
该方法检查给定任务类的构造函数并从HTTP请求(或者其它`ArrayAccess`对象)中解析变量来填充任务需要的构造函数参数。所以,如果我们的任务类在构造函数中接收一个`productId`变量,该任务将会尝试从HTTP请求中获取`productId`参数。
你还可以传递一个数组作为`dispatchFrom`方法的第三个参数。该数组用于填充所有请求中不存在的构造函数参数:
~~~
$this->dispatchFrom('App\Jobs\ProcessOrder', $request, [
'taxPercentage' => 20,
]);
~~~
## 4、运行队列监听器
**开启任务监听器**
Laravel包含了一个Artisan命令用来运行推送到队列的新任务。你可以使用`queue:listen`命令运行监听器:
~~~
php artisan queue:listen
~~~
还可以指定监听器使用哪个队列连接:
~~~
php artisan queue:listen connection
~~~
注意一旦任务开始后,将会持续运行直到手动停止。你可以使用一个过程监视器如[Supervisor](http://supervisord.org/)来确保队列监听器没有停止运行。
**队列优先级**
你可以传递逗号分隔的队列连接列表到`listen`任务来设置队列优先级:
~~~
php artisan queue:listen --queue=high,low
~~~
在本例中,`high`队列上的任务总是在从`low`队列移动任务之前被处理。
**指定任务超时参数**
你还可以设置每个任务允许运行的最大时间(以秒为单位):
~~~
php artisan queue:listen --timeout=60
~~~
**指定队列睡眠时间**
此外,可以指定轮询新任务之前的等待时间(以秒为单位):
~~~
php artisan queue:listen --sleep=5
~~~
需要注意的是队列只会在队列上没有任务时“睡眠”,如果存在多个有效任务,该队列会持续运行,从不睡眠。
### 4.1 Supervisor配置
Supervisor为Linux操作系统提供的进程监视器,将会在失败时自动重启`queue:listen`或`queue:work`命令,要在Ubuntu上安装Supervisor,使用如下命令:
~~~
sudo apt-get install supervisor
~~~
Supervisor配置文件通常存放在`/etc/supervisor/conf.d`目录,在该目录中,可以创建多个配置文件指示Supervisor如何监视进程,例如,让我们创建一个开启并监视`queue:work`进程的`laravel-worker.conf`文件:
~~~
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /home/forge/app.com/artisan queue:work sqs --sleep=3 --tries=3 --daemon
autostart=true
autorestart=true
user=forge
numprocs=8
redirect_stderr=true
stdout_logfile=/home/forge/app.com/worker.log
~~~
在本例中,`numprocs`指令让Supervisor运行8个`queue:work`进程并监视它们,如果失败的话自动重启。配置文件创建好了之后,可以使用如下命令更新Supervisor配置并开启进程:
~~~
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start laravel-worker:*
~~~
要了解更多关于Supervisor的使用和配置,查看[Supervisor文档](http://supervisord.org/index.html)。此外,还可以使用Laravel Forge从web接口方便地自动配置和管理Supervisor配置。
### 4.2 后台队列监听器
Artisan命令`queue:work`包含一个`--daemon`选项来强制队列worker持续处理任务而不必重新启动框架。相较于`queue:listen`命令该命令对CPU的使用有明显降低:
~~~
php artisan queue:work connection --daemon
php artisan queue:work connection --daemon --sleep=3
php artisan queue:work connection --daemon --sleep=3 --tries=3
~~~
正如你所看到的,`queue:work`任务支持大多数`queue:listen`中有效的选项。你可以使用`php artisan help queue:work`任务来查看所有有效选项。
### 4.2.1 后台队列监听器编码考虑
后台队列worker在处理每个任务时不重启框架,因此,你要在任务完成之前释放资源,举个例子,如果你在使用GD库操作图片,那么就在完成时使用`imagedestroy`释放内存。
类似的,数据库连接应该在后台长时间运行完成后断开,你可以使用`DB::reconnect`方法确保获取了一个新的连接。
### 4.3 部署后台队列监听器
由于后台队列worker是常驻进程,不重启的话不会应用代码中的更改,所以,最简单的部署后台队列worker的方式是使用部署脚本重启所有worker,你可以通过在部署脚本中包含如下命令重启所有worker:
~~~
php artisan queue:restart
~~~
该命令会告诉所有队列worker在完成当前任务处理后重启以便没有任务被遗漏。
> 注意:这个命令依赖于缓存系统重启进度表,默认情况下,APC在CLI任务中无法正常工作,如果你在使用APC,需要在APC配置中添加`apc.enable_cli=1`。
## 5、处理失败任务
由于事情并不总是按照计划发展,有时候你的队列任务会失败。别担心,它发生在我们大多数人身上!Laravel包含了一个方便的方式来指定任务最大尝试执行次数,任务执行次数达到最大限制后,会被插入到`failed_jobs`表,失败任务的名字可以通过配置文件`config/queue.php`来配置。
要创建一个failed_jobs表的迁移,可以使用queue:failed-table命令:
~~~
php artisan queue:failed-table
~~~
运行[队列监听器](http://laravelacademy.org/post/222.html#running-the-queue-listener)的时候,可以在`queue:listen`命令上使用`--tries`开关来指定任务最大可尝试执行次数:
~~~
php artisan queue:listen connection-name --tries=3
~~~
### 5.1 失败任务事件
如果你想要注册一个队列任务失败时被调用的事件,可以使用`Queue::failing`方法,该事件通过邮件或[HipChat](https://www.hipchat.com/)通知团队。举个例子,我么可以在Laravel自带的`AppServiceProvider`中附件一个回调到该事件:
~~~
<?php
namespace App\Providers;
use Queue;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider{
/**
* 启动应用服务
*
* @return void
*/
public function boot()
{
Queue::failing(function ($connection, $job, $data) {
// Notify team of failing job...
});
}
/**
* 注册服务提供者
*
* @return void
*/
public function register()
{
//
}
}
~~~
### 5.1.1 任务类的失败方法
想要更加细粒度的控制,可以在队列任务类上直接定义`failed`方法,从而允许你在失败发生时执行指定动作:
~~~
<?php
namespace App\Jobs;
use App\Jobs\Job;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Bus\SelfHandling;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendReminderEmail extends Job implements SelfHandling, ShouldQueue
{
use InteractsWithQueue, SerializesModels;
/**
* 执行任务
*
* @param Mailer $mailer
* @return void
*/
public function handle(Mailer $mailer)
{
//
}
/**
* 处理失败任务
*
* @return void
*/
public function failed()
{
// Called when the job is failing...
}
}
~~~
### 5.2 重试失败任务
要查看已插入到`failed_jobs`数据表中的所有失败任务,可以使用Artisan命令`queue:failed`:
~~~
php artisan queue:failed
~~~
该命令将会列出任务ID,连接,对列和失败时间,任务ID可用于重试失败任务,例如,要重试一个ID为5的失败任务,要用到下面的命令:
~~~
php artisan queue:retry 5
~~~
如果你要删除一个失败任务,可以使用`queue:forget`命令:
~~~
php artisan queue:forget 5
~~~
要删除所有失败任务,可以使用`queue:flush`命令:
~~~
php artisan queue:flush
~~~
服务 ―― 分页
最后更新于:2022-04-01 04:11:27
# 服务 —— 分页
## 1、简介
在其他框架中,分页是件非常痛苦的事,Laravel则使其变得轻而易举。Laravel能够基于当前页智能生成一定范围的链接,且生成的HTML兼容[Bootstrap CSS 框架](http://getbootstrap.com/)。
## 2、基本使用
### 2.1 基于查询构建器分页
有多种方式实现分页,最简单的方式就是使用[查询构建器](http://laravelacademy.org/post/126.html)或Eloquent模型的`paginate`方法。该方法基于当前用户查看页自动设置合适的偏移(offset)和限制(limit)。默认情况下,当前页通过HTTP请求查询字符串参数`?page`的值判断。当然,该值由Laravel自动检测,然后自动插入分页器生成的链接中。
让我们先来看看如何在查询上调用`paginate`方法。在本例中,传递给`paginate`的唯一参数就是你每页想要显示的数目,这里我们指定每页显示`15`个:
~~~
<?php
namespace App\Http\Controllers;
use DB;
use App\Http\Controllers\Controller;
class UserController extends Controller{
/**
* 显示应用中的所有用户
*
* @return Response
*/
public function index()
{
$users = DB::table('users')->paginate(15);
return view('user.index', ['users' => $users]);
}
}
~~~
> 注意:目前,使用`groupBy`的分页操作不能被Laravel有效执行,如果你需要在分页结果中使用`groupBy`,推荐你手动查询数据库然后创建分页器。
### 2.1.1 “简单分页”
如果你只需要在分页视图中简单的显示“下一个”和“上一个”链接,可以使用`simplePaginate`方法来执行该查询。在渲染包含大数据集的视图且不需要显示每个页码时非常有用:
~~~
$users = DB::table('users')->simplePaginate(15);
~~~
### 2.2 基于Eloquent模型分页
你还可以对[Eloquent](http://laravelacademy.org/post/138.html)查询结果进行分页,在本例中,我们对`User`模型进行分页,每页显示`15`条记录。正如你所看到的,该语法和基于查询构建器的分页差不多:
~~~
$users = App\User::paginate(15);
~~~
当然,你可以在设置其它约束调价之后调用`paginate`,比如`where`子句:
~~~
$users = User::where('votes', '>', 100)->paginate(15);
~~~
你也可以使用`simplePaginate`方法:
~~~
$users = User::where('votes', '>', 100)->simplePaginate(15);
~~~
### 2.3 手动创建分页器
有时候你可能想要通过传递数组数据来手动创建分页实例,你可以基于自己的需求通过创建`Illuminate\Pagination\Paginator`或`Illuminate\Pagination\LengthAwarePaginator`实例来实现。
`Paginator`类不需要知道结果集中数据项的总数;然而,正因如此,该类也没有提供获取最后一页索引的方法。
`LengthAwarePaginator`接收参数和`Paginator`几乎一样,只是,它要求传入结果集的总数。
换句话说,`Paginator` 对应`simplePaginate`方法,而`LengthAwarePaginator`对应`paginate`方法。
当手动创建分页器实例的时候,应该手动对传递到分页器的结果集进行“切片”,如果你不确定怎么做,查看PHP函数[array_slice](http://php.net/manual/en/function.array-slice.php)。
## 3、在视图中显示分页结果
当你调用查询构建器或Eloquent查询上的`paginate`或`simplePaginate`方法时,你将会获取一个分页器实例。当调用`paginate`方法时,你将获取`Illuminate\Pagination\LengthAwarePaginator`,而调用方法`simplePaginate`时,将会获取`Illuminate\Pagination\Paginator`实例。这些对象提供相关方法描述这些结果集,除了这些帮助函数外,分页器实例本身就是迭代器,可以像数组一样对其进行循环调用。
所以,获取到结果后,可以按如下方式使用[Blade](http://laravelacademy.org/post/79.html)显示这些结果并渲染页面链接:
~~~
<div class="container">
@foreach ($users as $user)
{{ $user->name }}
@endforeach
</div>
{!! $users->render() !!}
~~~
`render`方法将会将结果集中的其它页面链接渲染出来。每个链接已经包含了`?page`查询字符串变量。记住,`render`方法生成的HTML兼容[Bootstrap CSS 框架](https://getbootstrap.com/)。
> 注意:我们从Blade模板调用`render`方法时,确保使用`{!! !!}`语法以便HTML链接不被过滤。
### 3.1 自定义分页器URI
`setPath`方法允许你生成分页链接时自定义分页器使用的URI,例如,如果你想要分页器生成形如`http://example.com/custom/url?page=N`的链接,应该传递`custom/url`到`setPath`方法:
~~~
Route::get('users', function () {
$users = App\User::paginate(15);
$users->setPath('custom/url');
//
});
~~~
### 3.2 添加参数到分页链接
你可以使用`appends`方法添加查询参数到分页链接查询字符串。例如,要添加`&sort=votes`到每个分页链接,应该像如下方式调用`appends`:
~~~
{!! $users->appends(['sort' => 'votes'])->render() !!}
~~~
如果你想要添加”哈希片段”到分页链接,可以使用`fragment`方法。例如,要添加`#foo`到每个分页链接的末尾,像这样调用`fragment`方法:
~~~
{!! $users->fragment('foo')->render() !!}
~~~
### 3.3 更多帮助方法
你还可以通过如下分页器实例上的方法访问更多分页信息:
* `$results->count()`
* `$results->currentPage()`
* `$results->hasMorePages()`
* `$results->lastPage() (使用simplePaginate时无效)`
* `$results->nextPageUrl()`
* `$results->perPage()`
* `$results->total() (使用simplePaginate时无效)`
* `$results->url($page)`
## 4、将结果转化为JSON
Laravel分页器结果类实现了`Illuminate\Contracts\Support\JsonableInterface`[契约](http://laravelacademy.org/post/95.html)并实现`toJson`方法,所以将分页结果转化为JSON非常简单。
你还可以简单通过从路由或控制器动作返回分页器实例将转其化为JSON:
~~~
Route::get('users', function () {
return App\User::paginate();
});
~~~
从分页器转化来的JSON包含了元信息如`total`, `current_page`,`last_page`等等,实际的结果对象数据可以通过该JSON数组中的`data`键访问。下面是一个通过从路由返回的分页器实例创建的JSON例子:
~~~
{
"total": 50,
"per_page": 15,
"current_page": 1,
"last_page": 4,
"next_page_url": "http://laravel.app?page=2",
"prev_page_url": null,
"from": 1,
"to": 15,
"data":[
{
// Result Object
},
{
// Result Object
}
]
}
~~~
服务 ―― 包开发
最后更新于:2022-04-01 04:11:25
# 服务 —— 包开发
## 1、简介
包是添加功能到Laravel的主要方式。包可以提供任何功能,小到处理日期如[Carbon](https://github.com/briannesbitt/Carbon),大到整个BDD测试框架如[Behat](https://github.com/Behat/Behat)。
当然,有很多不同类型的包。有些包是独立的,意味着可以在任何框架中使用,而不仅是Laravel。比如Carbon和Behat都是独立的包。所有这些包都可以通过在`composer.json`文件中请求以便被Laravel使用。
另一方面,其它包只能特定和Laravel一起使用,这些包可能有路由用于加强Laravel应用的功能,本章主要讨论只能在Laravel中使用的包。
## 2、服务提供者
[服务提供者](http://laravelacademy.org/post/91.html)是包和Laravel之间的连接点。服务提供者负责绑定对象到Laravel的[服务容器](http://laravelacademy.org/post/93.html)并告知Laravel从哪里加载包资源如视图、配置和本地化文件。
服务提供者继承自`Illuminate\Support\ServiceProvider`类并包含两个方法:`register`和`boot`。`ServiceProvider`基类位于Composer包`illuminate/support`。
要了解更多关于服务提供者的内容,查看其[文档](http://laravelacademy.org/post/91.html)。
## 3、路由
要定义包的路由,只需要在包服务提供者中的`boot`方法中引入路由文件。在路由文件中,可以使用`Route`门面[注册路由](http://laravelacademy.org/post/53.html),和Laravel应用中注册路由一样:
~~~
/**
* Perform post-registration booting of services.
*
* @return void
*/
public function boot(){
if (! $this->app->routesAreCached()) {
require __DIR__.'/../../routes.php';
}
}
~~~
## 4、资源
### 4.1 视图
要在Laravel中注册包[视图](http://laravelacademy.org/post/76.html),需要告诉Laravel视图在哪,可以使用服务提供者的`loadViewsFrom`方法来实现。`loadViewsFrom`方法接收两个参数:视图模板的路径和包名称。例如,如果你的包名称是“courier”,添加如下代码到服务提供者的`boot`方法:
~~~
/**
* Perform post-registration booting of services.
*
* @return void
*/
public function boot(){
$this->loadViewsFrom(__DIR__.'/path/to/views', 'courier');
}
~~~
包视图通过使用类似的`package::view`语法来引用。所以,你可以通过如下方式加载`courier`包上的`admin`视图:
~~~
Route::get('admin', function () {
return view('courier::admin');
});
~~~
### 4.1.1 覆盖包视图
当你使用`loadViewsFrom`方法的时候,Laravel实际上为视图注册了两个存放位置:一个是`resources/views/vendor`目录,另一个是你指定的目录。所以,以`courier`为例:当请求一个包视图时,Laravel首先检查开发者是否在`resources/views/vendor/courier`提供了自定义版本的视图,如果该视图不存在,Laravel才会搜索你调用`loadViewsFrom`方法时指定的目录。这种机制使得终端用户可以轻松地自定义/覆盖包视图。
### 4.1.2 发布视图
如果你想要视图能够发布到应用的`resources/views/vendor`目录,可以使用服务提供者的`publishes`方法。该方法接收包视图路径及其相应的发布路径数组作为参数:
~~~
/**
* Perform post-registration booting of services.
*
* @return void
*/
public function boot(){
$this->loadViewsFrom(__DIR__.'/path/to/views', 'courier');
$this->publishes([
__DIR__.'/path/to/views' => base_path('resources/views/vendor/courier'),
]);
}
~~~
现在,当包用户执行Laravel的Artisan命令`vendor:publish`时,你的视图包将会被拷贝到指定路径。
### 4.2 翻译
如果你的包包含[翻译文件](http://laravelacademy.org/post/211.html),你可以使用`loadTranslationsFrom`方法告诉Laravel如何加载它们,例如,如果你的包命名为“courier”,你应该添加如下代码到服务提供者的`boot`方法:
~~~
/**
* Perform post-registration booting of services.
*
* @return void
*/
public function boot(){
$this->loadTranslationsFrom(__DIR__.'/path/to/translations', 'courier');
}
~~~
包翻译使用形如`package::file.line`的语法进行引用。所以,你可以使用如下方式从`messages`文件中加载`courier`包的`welcome`行:
~~~
echo trans('courier::messages.welcome');
~~~
### 4.3 配置
通常,你想要发布包配置文件到应用根目录下的`config`目录,这将允许包用户轻松覆盖默认配置选项,要发布一个配置文件,只需在服务提供者的`boot`方法中使用`publishes`方法即可:
~~~
/**
* Perform post-registration booting of services.
*
* @return void
*/
public function boot(){
$this->publishes([
__DIR__.'/path/to/config/courier.php' => config_path('courier.php'),
]);
}
~~~
现在,当包用户执行Laravel的Artisan命令`vendor:publish`时,你的文件将会被拷贝到指定位置,当然,配置被发布后,可以通过和其它配置选项一样的方式进行访问:
~~~
$value = config('courier.option');
~~~
### 4.3.1 默认包配置
你还可以选择将自己的包配置文件合并到应用的拷贝版本,这允许用户只引入他们在应用配置文件中实际想要覆盖的配置选项。要合并两个配置,在服务提供者的`register`方法中使用`mergeConfigFrom`方法即可:
~~~
/**
* Register bindings in the container.
*
* @return void
*/
public function register(){
$this->mergeConfigFrom(
__DIR__.'/path/to/config/courier.php', 'courier'
);
}
~~~
## 5、公共前端资源
你的包可能包含JavaScript、CSS和图片,要发布这些前端资源到应用根目录下的`public`目录,使用服务提供者的`publishes`方法。在本例中,我们添加一个前端资源组标签`public`,用于发布相关的前端资源组:
~~~
/**
* Perform post-registration booting of services.
*
* @return void
*/
public function boot(){
$this->publishes([
__DIR__.'/path/to/assets' => public_path('vendor/courier'),
], 'public');
}
~~~
现在,当包用户执行`vendor:publish`命令时,前端资源将会被拷贝到指定位置,由于你需要在每次包更新时重写前端资源,可以使用`--force`标识:
~~~
php artisan vendor:publish --tag=public --force
~~~
如果你想要确保前端资源已经更新到最新版本,可添加该命令到`composer.json`文件的`post-update-cmd`列表。
## 6、发布文件组
你可能想要分开发布包前端资源组和资源,例如,你可能想要用户发布包配置的同时不发布包前端资源,可以通过在调用时给它们打上“标签”来实现分离。下面我们在包服务提供者的`boot`方法中定义两个公共组:
~~~
/**
* Perform post-registration booting of services.
*
* @return void
*/
public function boot(){
$this->publishes([
__DIR__.'/../config/package.php' => config_path('package.php')
], 'config');
$this->publishes([
__DIR__.'/../database/migrations/' => database_path('migrations')
], 'migrations');
}
~~~
现在用户可以在使用Artisan命令`vendor:publish`时通过引用标签名来分开发布这两个组:
~~~
php artisan vendor:publish --provider="Vendor\Providers\PackageServiceProvider" --tag="config"
~~~
> 扩展阅读:[实例教程 —— 如何在Laravel 5.1中进行自定义包开发](http://laravelacademy.org/post/1219.html)
服务 ―― 邮件
最后更新于:2022-04-01 04:11:22
# 服务 —— 邮件
## 1、简介
Laravel基于目前流行的[SwiftMailer](http://swiftmailer.org/)库提供了一套干净清爽的邮件、PHP的`mail`函数,以及`sendmail`提供了驱动,从而允许你快速通过本地或云服务发送邮件。
### 1.1 邮件驱动预备知识
基于驱动的API如Mail HTTP库。你可以通过添加如下行到`composer.json`文件来安装Guzzle到项目:
~~~
"guzzlehttp/guzzle": "~5.3|~6.0"
~~~
### 1.1.1 Mailgun驱动
要使用Mailgun驱动,首先安装Guzzle,然后在配置文件`config/mail.php`中设置`driver`选项为`mailgun`。接下来,验证配置文件`config/services.php`包含如下选项:
~~~
'mailgun' => [
'domain' => 'your-mailgun-domain',
'secret' => 'your-mailgun-key',],
~~~
### 1.1.2 Mandrill驱动
要使用Mandrill驱动,首先安装Guzzle,然后在配置文件`config/mail.php`中设置`driver`选项值为`mandrill`。接下来,验证配置文件`config/services.php`包含如下选项:
~~~
'mandrill' => [
'secret' => 'your-mandrill-key',],
~~~
### 1.1.3 SES驱动
要使用Amazon SES驱动,安装Amazon AWS的PHP SDK,你可以通过添加如下行到`composer.json`文件的`require`部分来安装该库:
~~~
"aws/aws-sdk-php": "~3.0"
~~~
接下来,设置配置文件`config/mail.php`中的`driver`选项为`ses`。然后,验证配置文件`config/services.php`包含如下选项:
~~~
'ses' => [
'key' => 'your-ses-key',
'secret' => 'your-ses-secret',
'region' => 'ses-region', // e.g. us-east-1
],
~~~
## 2、发送邮件
Laravel允许你在[视图](http://laravelacademy.org/post/76.html)中存储邮件信息,例如,要组织你的电子邮件,可以在`resources/views`目录下创建`emails`目录。
要发送一条信息,使用`Mail`[门面](http://laravelacademy.org/post/97.html)上的`send`方法。`send`方法接收三个参数。第一个参数是包含邮件信息的视图名称;第二个参数是你想要传递到该视图的数组数据;第三个参数是接收消息实例的闭包回调——允许你自定义收件人、主题以及邮件其他方面的信息:
~~~
<?php
namespace App\Http\Controllers;
use Mail;
use App\User;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class UserController extends Controller{
/**
* 发送邮件给用户
*
* @param Request $request
* @param int $id
* @return Response
*/
public function sendEmailReminder(Request $request, $id)
{
$user = User::findOrFail($id);
Mail::send('emails.reminder', ['user' => $user], function ($m) use ($user) {
$m->to($user->email, $user->name)->subject('Your Reminder!');
});
}
}
~~~
由于我们在上例中传递一个包含`user`键的数组,我们可以在邮件中使用如下方式显示用户名:
~~~
<?php echo $user->name; ?>
~~~
> 注意:`$message`变量总是被传递到邮件视图,并允许嵌入附件,因此,你应该在视图负载中避免传入消息变量。
**构造消息**
正如前面所讨论的,传递给`send`方法的第三个参数是一个允许你指定邮件消息本身多个选项的闭包。使用这个闭包可以指定消息的其他属性,例如抄送、群发,等等:
~~~
Mail::send('emails.welcome', $data, function ($message) {
$message->from('us@example.com', 'Laravel');
$message->to('foo@example.com')->cc('bar@example.com');
});
~~~
下面试`$message`消息构建器实例上的可用方法:
~~~
$message->from($address, $name = null);
$message->sender($address, $name = null);
$message->to($address, $name = null);
$message->cc($address, $name = null);
$message->bcc($address, $name = null);
$message->replyTo($address, $name = null);
$message->subject($subject);
$message->priority($level);
$message->attach($pathToFile, array $options = []);
// 从$data字符串追加文件...
$message->attachData($data, $name, array $options = []);
// 获取底层SwiftMailer消息实例...
$message->getSwiftMessage();
~~~
> 注意:传递给`Mail::send`闭包的消息实例继承自`SwiftMailer`消息类,该实例允许你调用该类上的任何方法来构建自己的电子邮件消息。
**纯文本邮件**
默认情况下,传递给`send`方法的视图假定包含HTML,然而,通过传递数组作为第一个参数到`send`方法,你可以指定发送除HTML视图之外的纯文本视图:
~~~
Mail::send(['html.view', 'text.view'], $data, $callback);
~~~
或者,如果你只需要发送纯文本邮件,可以指定在数组中使用text键:
~~~
Mail::send(['text' => 'view'], $data, $callback);
~~~
**原生字符串邮件**
如果你想要直接发送原生字符串邮件你可以使用`raw`方法:
~~~
Mail::raw('Text to e-mail', function ($message) {
//
});
~~~
### 2.1 附件
要添加附件到邮件,使用传递给闭包的`$message`对象上的`attach`方法。该方法接收文件的绝对路径作为第一个参数:
~~~
Mail::send('emails.welcome', $data, function ($message) {
//
$message->attach($pathToFile);
});
~~~
当添加文件到消息时,你还可以通过传递数组作为第二个参数到`attach`方法来指定文件显示名和MIME类型:
~~~
$message->attach($pathToFile, ['as' => $display, 'mime' => $mime]);
~~~
### 2.2 内联附件
### 2.2.1 在邮件视图中嵌入一张图片
嵌套内联图片到邮件中通常是很笨重的,然而,Laravel提供了一个便捷的方式附加图片到邮件并获取相应的CID,要嵌入内联图片,在邮件视图中使用`$message`变量上的`embed`方法。记住,Laravel自动在所有邮件视图中传入`$message`变量使其有效:
~~~
<body>
Here is an image:
<img src="<?php echo $message->embed($pathToFile); ?>">
</body>
~~~
### 2.2.2 在邮件视图中嵌入原生数据
如果你想要在邮件消息中嵌入原生数据字符串,可以使用`$message`变量上的`embedData`方法:
~~~
<body>
Here is an image from raw data:
<img src="<?php echo $message->embedData($data, $name); ?>">
</body>
~~~
### 2.3 邮件队列
### 2.3.1 邮件消息队列
发送邮件消息可能会大幅度延长应用的响应时间,许多开发者选择将邮件发送放到队列中再后台执行,Laravel中可以使用内置的[统一队列API](http://laravelacademy.org/post/222.html)来实现。要将邮件消息放到队列中,使用`Mail`门面上的`queue`方法:
~~~
Mail::queue('emails.welcome', $data, function ($message) {
//
});
~~~
该方法自动将邮件任务推送到队列中以便在后台发送。当然,你需要在使用该特性前[配置队列](http://laravelacademy.org/post/222.html)。
### 2.3.2 延迟消息队列
如果你想要延迟已经放到队列中邮件的发送,可以使用`later`方法。只需要传递你想要延迟发送的秒数作为第一个参数到该方法即可:
~~~
Mail::later(5, 'emails.welcome', $data, function ($message) {
//
});
~~~
### 2.3.3 推入指定队列
如果你想要将邮件消息推送到指定队列,可以使用`queueOn`和`laterOn`方法:
~~~
Mail::queueOn('queue-name', 'emails.welcome', $data, function ($message) {
//
});
Mail::laterOn('queue-name', 5, 'emails.welcome', $data, function ($message) {
//
});
~~~
## 3、邮件&本地开发
开发发送邮件的应用时,你可能不想要真的发送邮件到有效的电子邮件地址,而只是想要做下测试。Laravel提供了几种方式“禁止”邮件的实际发送。
### 3.1 日志驱动
一种解决方案是在本地开发时使用`log`邮件驱动。该驱动将所有邮件信息写到日志文件中以备查看,想要了解更多关于每个环境的应用配置信息,查看[配置文档](http://laravelacademy.org/post/46.html#environment-configuration)。
### 3.2 通用配置
Laravel提供的另一种解决方案是为框架发送的所有邮件设置通用收件人,这样的话,所有应用生成的邮件将会被发送到指定地址,而不是实际发送邮件指定的地址。这可以通过在配置文件`config/mail.php`中设置`to`选项来实现:
~~~
'to' => [
'address' => 'dev@domain.com',
'name' => 'Dev Example'
],
~~~
### 3.3 Mailtrap
最后,你可以使用[Mailtrap](https://mailtrap.io/)服务和`smtp`驱动发送邮件信息到“虚拟”邮箱,这种方法允许你在Mailtrap的消息查看器中查看最终的邮件。
服务 ―― 本地化
最后更新于:2022-04-01 04:11:20
# 服务 —— 本地化
## 1、简介
Laravel的本地化特性提供了一个方便的方式从多个语言文件中获取字符串,从而允许你在应用中轻松支持多种语言。
语言字符串存放在`resources/lang`目录中,在该目录中应该包含应用支持的每种语言的子目录:
~~~
/resources
/lang
/en
messages.php
/es
messages.php
~~~
所有语言文件都返回一个键值对数组,例如:
~~~
<?php
return [
'welcome' => 'Welcome to our application'
];
~~~
### 1.1 配置Locale选项
应用默认语言存放在配置文件`config/app.php`中,当然,你可以修改该值来匹配应用需要。你还可以在运行时使用`App`[门面](http://laravelacademy.org/post/97.html)上的`setLocale`方法改变当前语言:
~~~
Route::get('welcome/{locale}', function ($locale) {
App::setLocale($locale);
//
});
~~~
你还可以配置一个“备用语言”,当当前语言不包含给定语言行时备用语言被返回。和默认语言一样,备用语言也在配置文件`config/app.php`中配置:
~~~
'fallback_locale' => 'en',
~~~
## 2、基本使用
你可以使用帮助函数`trans`从语言文件中获取行,该方法接收文件和语言行的键作为第一个参数,例如,让我们在语言文件`resources/lang/messages.php`中获取语言行`welcome`:
~~~
echo trans('messages.welcome');
~~~
当然如果你使用[Blade模板引擎](http://laravelacademy.org/post/79.html),可以使用{{ }}语法打印语言行:
~~~
{{ trans('messages.welcome') }}
~~~
如果指定的语言行不存在,`trans`函数将返回语言行的键,所以,使用上面的例子,如果语言行不存在的话,`trans`函数将返回`messages.welcome`。
**替换语言行中的参数**
如果需要的话,你可以在语言行中定义占位符,所有的占位符都有一个:前缀,例如,你可以用占位符名称定义一个`welcome`消息:
~~~
'welcome' => 'Welcome, :name',
~~~
要在获取语言行的时候替换占位符,传递一个替换数组作为`trans`函数的第二个参数:
~~~
echo trans('messages.welcome', ['name' => 'Dayle']);
~~~
### 2.1 多元化
多元化是一个复杂的问题,因为不同语言对多元化有不同的规则,通过使用管道字符“|”,你可以区分一个字符串的单数和复数形式:
~~~
'apples' => 'There is one apple|There are many apples',
~~~
然后,你可以使用`trans_choice`函数获取给定行数的语言行,在本例中,由于行数大于1,将会返回语言行的复数形式:
~~~
echo trans_choice('messages.apples', 10);
~~~
由于Laravel翻译器由Symfony翻译组件提供,你可以创建更复杂的多元化规则:
~~~
'apples' => '{0} There are none|[1,19] There are some|[20,Inf] There are many',
~~~
## 3、覆盖Vendor包中语言文件
有些包可以处理自己的语言文件。你可以通过将自己的文件放在`resources/lang/vendor/{package}/{locale}`目录下来覆盖它们而不是破坏这些包的核心文件来调整这些句子。
所以,举个例子,如果你需要覆盖名为`skyrim/hearthfire`的包中的`messages.php`文件里的英文句子,可以创建一个`resources/lang/vendor/hearthfire/en/messages.php`文件。在这个文件中只需要定义你想要覆盖的句子,你没有覆盖的句子仍然从该包原来的语言文件中加载。
服务 ―― 帮助函数
最后更新于:2022-04-01 04:11:18
# 服务 —— 帮助函数
## 1、简介
Laravel自带了一系列PHP帮助函数,很多被框架自身使用,然而,如果你觉得方便的话也可以在应用中随心所欲的使用它们。
## 2、 数组函数
### array_add()
`array_add`函数添加给定键值对到数组,如果给定键不存在的话:
~~~
$array = array_add(['name' => 'Desk'], 'price', 100);
// ['name' => 'Desk', 'price' => 100]
~~~
### array_divide()
`array_divide`函数返回两个数组,一个包含原数组的所有键,另外一个包含原数组的所有值:
~~~
list($keys, $values) = array_divide(['name' => 'Desk']);
// $keys: ['name']
// $values: ['Desk']
~~~
### array_dot()
`array_dot`函数使用”.“号将将多维数组转化为一维数组:
~~~
$array = array_dot(['foo' => ['bar' => 'baz']]);
// ['foo.bar' => 'baz'];
~~~
### array_except()
`array_except`方法从数组中移除给定键值对:
~~~
$array = ['name' => 'Desk', 'price' => 100];
$array = array_except($array, ['price']);
// ['name' => 'Desk']
~~~
### array_first()
`array_first`方法返回通过测试数组的第一个元素:
~~~
$array = [100, 200, 300];
$value = array_first($array, function ($key, $value) {
return $value >= 150;});
// 200
~~~
默认值可以作为第三个参数传递给该方法,如果没有值通过测试的话返回默认值:
~~~
$value = array_first($array, $callback, $default);
~~~
### array_flatten()
`array_flatten`方法将多维数组转化为一维数组:
~~~
$array = ['name' => 'Joe', 'languages' => ['PHP', 'Ruby']];
$array = array_flatten($array);
// ['Joe', 'PHP', 'Ruby'];
~~~
### array_forget()
`array_forget`方法使用”.“号从嵌套数组中移除给定键值对:
~~~
$array = ['products' => ['desk' => ['price' => 100]]];
array_forget($array, 'products.desk');
// ['products' => []]
~~~
### array_get()
`array_get`方法使用”.“号从嵌套数组中获取值:
~~~
$array = ['products' => ['desk' => ['price' => 100]]];
$value = array_get($array, 'products.desk');
// ['price' => 100]
~~~
`array_get`函数还接收一个默认值,如果指定键不存在的话则返回该默认值:
~~~
$value = array_get($array, 'names.john', 'default');
~~~
### array_only()
`array_only`方法只从给定数组中返回指定键值对:
~~~
$array = ['name' => 'Desk', 'price' => 100, 'orders' => 10];
$array = array_only($array, ['name', 'price']);
// ['name' => 'Desk', 'price' => 100]
~~~
### array_pluck()
`array_pluck`方法从数组中返回给定键对应的键值对列表:
~~~
$array = [
['developer' => ['name' => 'Taylor']],
['developer' => ['name' => 'Abigail']]];
$array = array_pluck($array, 'developer.name');
// ['Taylor', 'Abigail'];
~~~
### array_pull()
`array_pull`方法从数组中返回并移除键值对:
~~~
$array = ['name' => 'Desk', 'price' => 100];
$name = array_pull($array, 'name');
// $name: Desk
// $array: ['price' => 100]
~~~
### array_set()
`array_set`方法在嵌套数组中使用”.“号设置值:
~~~
$array = ['products' => ['desk' => ['price' => 100]]];
array_set($array, 'products.desk.price', 200);
// ['products' => ['desk' => ['price' => 200]]]
~~~
### array_sort()
`array_sort`方法通过给定闭包的结果对数组进行排序:
~~~
$array = [
['name' => 'Desk'],
['name' => 'Chair'],
];
$array = array_values(array_sort($array, function ($value) {
return $value['name'];
}));
/*
[
['name' => 'Chair'],
['name' => 'Desk'],
]
*/
~~~
### array_sort_recursive()
`array_sort_recursive`函数使用`sort`函数对数组进行递归排序:
~~~
$array = [
[
'Roman',
'Taylor',
'Li',
],
[
'PHP',
'Ruby',
'JavaScript',
],
];
$array = array_sort_recursive($array);
/*
[
[
'Li',
'Roman',
'Taylor',
],
[
'JavaScript',
'PHP',
'Ruby',
]
];
*/
~~~
### array_where()
`array_where`函数使用给定闭包对数组进行排序:
~~~
$array = [100, '200', 300, '400', 500];
$array = array_where($array, function ($key, $value) {
return is_string($value);
});
// [1 => 200, 3 => 400]
~~~
### head()
`head`函数只是简单返回给定数组的第一个元素:
~~~
$array = [100, 200, 300];
$first = head($array);
// 100
~~~
### last()
`last`函数返回给定数组的最后一个元素:
~~~
$array = [100, 200, 300];
$last = last($array);
// 300
~~~
## 3、路径函数
### app_path()
`app_path`函数返回app目录的绝对路径:
~~~
$path = app_path();
~~~
你还可以使用`app_path`函数为相对于`app`目录的给定文件生成绝对路径:
~~~
$path = app_path('Http/Controllers/Controller.php');
~~~
### base_path()
`base_path`函数返回项目根目录的绝对路径:
~~~
$path = base_path();
~~~
你还可以使用`base_path`函数为相对于应用目录的给定文件生成绝对路径:
~~~
$path = base_path('vendor/bin');
~~~
### config_path()
`config_path`函数返回应用配置目录的绝对路径:
~~~
$path = config_path();
~~~
### database_path()
`database_path`函数返回应用数据库目录的绝对路径:
~~~
$path = database_path();
~~~
### public_path()
`public_path`函数返回`public`目录的绝对路径:
~~~
$path = public_path();
~~~
### storage_path()
`storage_path`函数返回`storage`目录的绝对路径:
~~~
$path = storage_path();
~~~
还可以使用`storage_path`函数生成相对于`storage`目录的给定文件的绝对路径:
~~~
$path = storage_path('app/file.txt');
~~~
## 4、字符串函数
### camel_case()
`camel_case`函数将给定字符串转化为按驼峰式命名规则的字符串:
~~~
$camel = camel_case('foo_bar');
// fooBar
~~~
### class_basename()
`class_basename`返回给定类移除命名空间后的类名:
~~~
$class = class_basename('Foo\Bar\Baz');
// Baz
~~~
### e()
`e`函数在给定字符串上运行`htmlentities`:
~~~
echo e('<html>foo</html>');
// <html>foo</html>
~~~
### ends_with()
`ends_with`函数判断给定字符串是否以给定值结尾:
~~~
$value = ends_with('This is my name', 'name');
// true
~~~
### snake_case()
`snake_case`函数将给定字符串转化为下划线分隔的字符串:
~~~
$snake = snake_case('fooBar');
// foo_bar
~~~
### str_limit()
`str_limit`函数限制输出字符串的数目,该方法接收一个字符串作为第一个参数以及该字符串最大输出字符数作为第二个参数:
~~~
$value = str_limit('The PHP framework for web artisans.', 7);
// The PHP...
~~~
### starts_with()
`starts_with`函数判断给定字符串是否以给定值开头:
~~~
$value = starts_with('This is my name', 'This');
// true
~~~
### str_contains()
`str_contains`函数判断给定字符串是否包含给定值:
~~~
$value = str_contains('This is my name', 'my');
// true
~~~
### str_finish()
`str_finish`函数添加字符到字符串结尾:
~~~
$string = str_finish('this/string', '/');
// this/string/
~~~
### str_is()
`str_is`函数判断给定字符串是否与给定模式匹配,星号可用于表示通配符:
~~~
$value = str_is('foo*', 'foobar');
// true
$value = str_is('baz*', 'foobar');
// false
~~~
### str_plural()
`str_plural`函数将字符串转化为复数形式,该函数当前只支持英文:
~~~
$plural = str_plural('car');
// cars
$plural = str_plural('child');
// children
~~~
### str_random()
`str_random`函数通过指定长度生成随机字符串:
~~~
$string = str_random(40);
~~~
### str_singular()
`str_singular`函数将字符串转化为单数形式,该函数目前只支持英文:
~~~
$singular = str_singular('cars');
// car
~~~
### str_slug()
`str_slug`函数将给定字符串生成URL友好的格式:
~~~
$title = str_slug("Laravel 5 Framework", "-");
// laravel-5-framework
~~~
### studly_case()
`studly_case`函数将给定字符串转化为单词开头字母大写的格式:
~~~
$value = studly_case('foo_bar');
// FooBar
~~~
### trans()
`trans`函数使用本地文件翻译给定语言行:
~~~
echo trans('validation.required'):
~~~
### trans_choice()
trans_choice函数翻译带拐点的给定语言行:
~~~
$value = trans_choice('foo.bar', $count);
~~~
## 5、URL函数
### action()
**action**函数为给定[控制器](http://laravelacademy.org/post/60.html)动作生成URL,你不需要传递完整的命名空间到该控制器,传递相对于命名空间App\Http\Controllers的类名即可:
~~~
$url = action('HomeController@getIndex');
~~~
如果该方法接收路由参数,你可以将其作为第二个参数传递进来:
~~~
$url = action('UserController@profile', ['id' => 1]);
~~~
### asset()
使用当前请求的scheme(HTTP或HTTPS)为前端资源生成一个URL:
~~~
$url = asset('img/photo.jpg');
~~~
### secure_asset()
使用HTTPS为前端资源生成一个URL:
~~~
echo secure_asset('foo/bar.zip', $title, $attributes = []);
~~~
### route()
`route`函数为给定[命名路由](http://laravelacademy.org/post/53.html#ipt_kb_toc_53_5)生成一个URL:
~~~
$url = route('routeName');
~~~
如果该路由接收参数,你可以将其作为第二个参数传递进来:
~~~
$url = route('routeName', ['id' => 1]);
~~~
### url()
`url`函数为给定路径生成绝对路径:
~~~
echo url('user/profile');
echo url('user/profile', [1]);
~~~
## 6、其它函数
### auth()
`auth`函数返回一个认证器实例,为方便起见你可以用其取代`Auth`门面:
~~~
$user = auth()->user();
~~~
### back()
`back`函数生成重定向响应到用户前一个位置:
~~~
return back();
~~~
### bcrypt()
`bcrypt`函数使用Bcrypt对给定值进行[哈希](http://laravelacademy.org/post/203.html),你可以用其替代`Hash`门面:
~~~
$password = bcrypt('my-secret-password');
~~~
### config()
`config`函数获取配置变量的值,配置值可以通过使用”.”号访问,包含文件名以及你想要访问的选项。如果配置选项不存在的话默认值将会被指定并返回:
~~~
$value = config('app.timezone');$value = config('app.timezone', $default);
~~~
帮助函数`config`还可以用于在运行时通过传递键值对数组设置配置变量值:
~~~
config(['app.debug' => true]);
~~~
### csrf_field()
`csrf_field`函数生成一个包含CSRF令牌值的HTML隐藏域,例如,使用[Blade语法](http://laravelacademy.org/post/79.html):
~~~
{!! csrf_field() !!}
~~~
### csrf_token()
`csrf_token`函数获取当前CSRF令牌的值:
~~~
$token = csrf_token();
~~~
### dd()
`dd`函数输出给定变量值并终止脚本执行:
~~~
dd($value);
~~~
### elixir()
`elixir`函数获取带版本号的[Elixir](http://laravelacademy.org/post/187.html)文件路径:
~~~
elixir($file);
~~~
### env()
`env`函数获取环境变量值或返回默认值:
~~~
$env = env('APP_ENV');
// Return a default value if the variable doesn't exist...
$env = env('APP_ENV', 'production');
~~~
### event()
`event`函数分发给定[事件](http://laravelacademy.org/post/198.html)到对应监听器:
~~~
event(new UserRegistered($user));
~~~
### factory()
`factory`函数为给定类、名称和数量创建模型工厂构建器,可用于测试或[数据填充](http://laravelacademy.org/post/133.html#ipt_kb_toc_133_2):
~~~
$user = factory('App\User')->make();
~~~
### method_field()
`method_field`函数生成包含HTTP请求方法的HTML隐藏域,例如:
~~~
<form method="POST">
{!! method_field('delete') !!}</form>
~~~
### old()
`old`函数获取一次性存放在session中的值:
~~~
$value = old('value');
~~~
### redirect()
`redirect`函数返回重定向器实例进行[重定向](http://laravelacademy.org/post/70.html#ipt_kb_toc_70_7):
~~~
return redirect('/home');
~~~
### response()
`response`函数创建一个[响应](http://laravelacademy.org/post/70.html)实例或者获取响应工厂实例:
~~~
return response('Hello World', 200, $headers);return response()->json(['foo' => 'bar'], 200, $headers)
~~~
### value()
`value`函数返回给定的值,然而,如果你传递一个闭包到该函数,该闭包将会被执行并返回执行结果:
~~~
$value = value(function() { return 'bar'; });
~~~
### view()
`view`函数获取一个[视图](http://laravelacademy.org/post/76.html)实例:
~~~
return view('auth.login');
~~~
### with()
`with`函数返回给定的值,该函数在方法链中特别有用,别的地方就没什么用了:
~~~
$value = with(new Foo)->work();
~~~
服务 ―― 哈希
最后更新于:2022-04-01 04:11:16
# 服务 —— 哈希
## 1、简介
Laravel `Hash使用该Bcrypt。
Bcrypt是散列密码的绝佳选择,因为其”工作因子“是可调整的,这意味着随着硬件功能的提升,生成哈希所花费的时间也会增加。
## 2、基本使用
可以调用`Hash`门面上的`make`方法散列存储密码:
~~~
<?php
namespace App\Http\Controllers;
use Hash;
use App\User;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class UserController extends Controller{
/**
* 更新用户密码
*
* @param Request $request
* @param int $id
* @return Response
*/
public function updatePassword(Request $request, $id)
{
$user = User::findOrFail($id);
// 验证新密码长度...
$user->fill([
'password' => Hash::make($request->newPassword)
])->save();
}
}
~~~
此外,还可以使用全局的帮助函数`bcrypt`:
~~~
bcrypt('plain-text');
~~~
### 2.1 验证哈希密码
`check`方法允许你验证给定原生字符串和给定哈希是否相等,然而,如果你在使用Laravel自带的`AuthController`(详见[用户认证](http://laravelacademy.org/post/163.html)一节),就不需要再直接使用该方法,因为自带的认证控制器自动调用了该方法:
~~~
if (Hash::check('plain-text', $hashedPassword)) {
// 密码匹配...
}
~~~
### 2.2 检查密码是否需要被重新哈希
`needsRehash`方法允许你判断哈希计算器使用的工作因子在上次密码被哈希后是否发生改变:
~~~
if (Hash::needsRehash($hashed)) {
$hashed = Hash::make('plain-text');
}
~~~
服务 ―― 文件系统/云存储
最后更新于:2022-04-01 04:11:13
# 服务 —— 文件系统/云存储
## 1、简介
基于Frank de Jonge的PHP包[Flysystem](https://github.com/thephpleague/flysystem),Laravel提供了强大的文件系统选项间切换非常简单,因为对每个系统而言,API是一样的。
## 2、配置
文件系统配置文件位于`config/filesystems.php`。在该文件中可以配置所有”硬盘“,每个硬盘描述了特定的存储驱动和存储位置。为每种支持的驱动的示例配置包含在该配置文件中,所以,简单编辑该配置来反映你的存储参数和认证信息。
当然,你想配置磁盘多少就配置多少,多个磁盘也可以共用同一个驱动。
### 2.1 本地驱动
使用`local`驱动的时候,注意所有文件操作相对于定义在配置文件中的`root`目录,默认情况下,该值设置为`storage/app`目录,因此,下面的方法将会存储文件到`storage/app/file.txt`:
~~~
Storage::disk('local')->put('file.txt', 'Contents');
~~~
### 2.2 其它驱动预备知识
在使用Amazon S3或Rackspace驱动之前,需要通过Composer安装相应的包:
* Amazon S3: `league/flysystem-aws-s3-v3 ~1.0`
* Rackspace: `league/flysystem-rackspace ~1.0`
## 3、基本使用
### 3.1 获取硬盘实例
`Storage`[门面](http://laravelacademy.org/post/97.html)用于和你配置的所有磁盘进行交互,例如,你可以使用该门面上的put方法来存储头像到默认磁盘,如果你调用`Storage`门面上的方法却先调用`disk`方法,该方法调用自动传递到默认磁盘:
~~~
<?php
namespace App\Http\Controllers;
use Storage;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class UserController extends Controller{
/**
* 更新指定用户头像
*
* @param Request $request
* @param int $id
* @return Response
*/
public function updateAvatar(Request $request, $id)
{
$user = User::findOrFail($id);
Storage::put(
'avatars/'.$user->id,
file_get_contents($request->file('avatar')->getRealPath())
);
}
}
~~~
使用多个磁盘时,可以使用`Storage`门面上的`disk`方法访问特定磁盘。当然,可以继续使用方法链执行该磁盘上的方法:
~~~
$disk = Storage::disk('s3');
$contents = Storage::disk('local')->get('file.jpg')
~~~
### 3.2 获取文件
`get`方法用于获取给定文件的内容,该方法将会返回该文件的原生字符串内容:
~~~
$contents = Storage::get('file.jpg');
~~~
`exists`方法用于判断给定文件是否存在于磁盘上:
~~~
$exists = Storage::disk('s3')->exists('file.jpg');
~~~
#### 3.2.1 文件元信息
`size`方法以字节方式返回文件大小:
~~~
$size = Storage::size('file1.jpg');
~~~
`lastModified`方法以UNIX时间戳格式返回文件最后一次修改时间:
~~~
$time = Storage::lastModified('file1.jpg');
~~~
### 3.3 存储文件
`put`方法用于存储文件到磁盘。可以传递一个PHP资源到`put`方法,该方法将会使用Flysystem底层的流支持。在处理大文件的时候推荐使用文件流:
~~~
Storage::put('file.jpg', $contents);
Storage::put('file.jpg', $resource);
~~~
`copy`方法将磁盘中已存在的文件从一个地方拷贝到另一个地方:
~~~
Storage::copy('old/file1.jpg', 'new/file1.jpg');
~~~
`move`方法将磁盘中已存在的文件从一定地方移到到另一个地方:
~~~
Storage::move('old/file1.jpg', 'new/file1.jpg');
~~~
### 3.3.1 添加内容到文件开头/结尾
`prepend`和`append`方法允许你轻松插入内容到文件开头/结尾:
~~~
Storage::prepend('file.log', 'Prepended Text');
Storage::append('file.log', 'Appended Text');
~~~
### 3.4 删除文件
`delete` 方法接收单个文件名或多个文件数组并将其从磁盘移除:
~~~
Storage::delete('file.jpg');
Storage::delete(['file1.jpg', 'file2.jpg']);
~~~
### 3.5 目录
### 3.5.1 获取一个目录下的所有文件
`files`方法返回给定目录下的所有文件数组,如果你想要获取给定目录下包含子目录的所有文件列表,可以使用`allFiles`方法:
~~~
$files = Storage::files($directory);
$files = Storage::allFiles($directory);
~~~
### 3.5.2 获取一个目录下的所有子目录
`directories`方法返回给定目录下所有目录数组,此外,可以使用`allDirectories`方法获取嵌套的所有子目录数组:
~~~
$directories = Storage::directories($directory);
// 递归...
$directories = Storage::allDirectories($directory);
~~~
### 3.5.3 创建目录
`makeDirectory`方法将会创建给定目录,包含子目录(递归):
~~~
Storage::makeDirectory($directory);
~~~
### 3.5.4 删除目录
最后,`deleteDirectory`方法用于移除目录,包括该目录下的所有文件:
~~~
Storage::deleteDirectory($directory);
~~~
## 4、自定义文件系统
Laravel的Flysystem集成支持自定义驱动,为了设置自定义的文件系统你需要创建一个[服务提供者](http://laravelacademy.org/post/91.html)如`DropboxServiceProvider`。在该提供者的`boot`方法中,你可以使用`Storage`门面的`extend`方法定义自定义驱动:
~~~
<?php
namespace App\Providers;
use Storage;
use League\Flysystem\Filesystem;
use Dropbox\Client as DropboxClient;
use Illuminate\Support\ServiceProvider;
use League\Flysystem\Dropbox\DropboxAdapter;
class DropboxServiceProvider extends ServiceProvider{
/**
* Perform post-registration booting of services.
*
* @return void
*/
public function boot()
{
Storage::extend('dropbox', function($app, $config) {
$client = new DropboxClient(
$config['accessToken'], $config['clientIdentifier']
);
return new Filesystem(new DropboxAdapter($client));
});
}
/**
* Register bindings in the container.
*
* @return void
*/
public function register()
{
//
}
}
~~~
`extend`方法的第一个参数是驱动名称,第二个参数是获取`$app`和`$config`变量的闭包。该解析器闭包必须返回一个`League\Flysystem\Filesystem`实例。$config变量包含了定义在配置文件`config/filesystems.php`中为特定磁盘定义的选项。
创建好注册扩展的服务提供者后,就可以使用配置文件`config/filesystem.php`中的`dropbox`驱动了。
服务 ―― 事件
最后更新于:2022-04-01 04:11:11
# 服务 —— 事件
## 1、简介
Laravel事件存放在`app/Listeners`。
## 2、注册事件/监听器
Laravel自带的`EventServiceProvider`为事件注册提供了方便之所。其中的`listen`属性包含了事件(键)和对应监听器(值)数组。如果应用需要,你可以添加多个事件到该数组。例如,让我们添加`PodcastWasPurchased`事件:
~~~
/**
* 事件监听器映射
*
* @var array
*/
protected $listen = [
'App\Events\PodcastWasPurchased' => [
'App\Listeners\EmailPurchaseConfirmation',
],
];
~~~
### 2.1 生成事件/监听器类
当然,手动为每个事件和监听器创建文件是很笨重的,取而代之地,我们可见简单添加监听器和事件到`EventServiceProvider`然后使用`event:generate`命令。该命令将会生成罗列在`EventServiceProvider`中的所有事件和监听器。当然,已存在的事件和监听器不会被创建:
~~~
php artisan event:generate
~~~
## 3、定义事件
事件类是一个处理与事件相关的简单数据容器,例如,假设我们生成的`PodcastWasPurchased`事件接收一个[Eloquent ORM](http://laravelacademy.org/post/138.html)对象:
~~~
<?php
namespace App\Events;
use App\Podcast;
use App\Events\Event;
use Illuminate\Queue\SerializesModels;
class PodcastWasPurchased extends Event{
use SerializesModels;
public $podcast;
/**
* 创建新的事件实例
*
* @param Podcast $podcast
* @return void
*/
public function __construct(Podcast $podcast)
{
$this->podcast = $podcast;
}
}
~~~
正如你所看到的,该事件类不包含任何特定逻辑,只是一个存放被购买的`Podcast`对象的容器,如果事件对象被序列化的话,事件使用的 `SerializesModels` trait将会使用PHP的`serialize`函数序列化所有Eloquent模型。
## 4、定义监听器
接下来,让我们看看我们的示例事件的监听器,事件监听器在`handle`方法中接收事件实例,`event:generate`命令将会自动在`handle`方法中导入合适的事件类和类型提示事件。在`handle`方法内,你可以执行任何需要的逻辑以响应事件。
~~~
<?php
namespace App\Listeners;
use App\Events\PodcastWasPurchased;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
class EmailPurchaseConfirmation{
/**
* 创建事件监听器
*
* @return void
*/
public function __construct()
{
//
}
/**
* 处理事件
*
* @param PodcastWasPurchased $event
* @return void
*/
public function handle(PodcastWasPurchased $event)
{
// Access the podcast using $event->podcast...
}
}
~~~
你的事件监听器还可以在构造器中类型提示任何需要的依赖,所有事件监听器通过[服务容器](http://laravelacademy.org/post/93.html)解析,所以依赖会自动注入:
~~~
use Illuminate\Contracts\Mail\Mailer;
public function __construct(Mailer $mailer){
$this->mailer = $mailer;
}
~~~
**停止事件继续往下传播**
有时候,你希望停止事件被传播到其它监听器,你可以通过从监听器的`handle`方法中返回`false`来实现。
### 4.1 事件监听器队列
需要将事件监听器放到[队列](http://laravelacademy.org/post/222.html)中?没有比这更简单的了,只需要让监听器类实现`ShouldQueue`接口即可,通过Artisan命令`event:generate`生成的监听器类已经将接口导入当前命名空间,所有你可以立即拿来使用:
~~~
<?php
namespace App\Listeners;
use App\Events\PodcastWasPurchased;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
class EmailPurchaseConfirmation implements ShouldQueue{
//
}
~~~
就是这么简单,当监听器被事件调用,将会使用Laravel的[队列系统](http://laravelacademy.org/post/222.html)通过队列分发器自动队列化。如果通过队列执行监听器的时候没有抛出任何异常,队列任务在执行完成后被自动删除。
### 4.1.1 手动访问队列
如果你需要手动访问底层队列任务的`delete`和`release`方法,在生成的监听器中默认导入的`Illuminate\Queue\InteractsWithQueue` trait提供了访问这两个方法的权限:
~~~
<?php
namespace App\Listeners;
use App\Events\PodcastWasPurchased;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
class EmailPurchaseConfirmation implements ShouldQueue{
use InteractsWithQueue;
public function handle(PodcastWasPurchased $event)
{
if (true) {
$this->release(30);
}
}
}
~~~
## 5、触发事件
要触发一个事件,可以使用Event[门面](http://laravelacademy.org/post/97.html),传递一个事件实例到`fire`方法,`fire`方法会分发事件到所有监听器:
~~~
<?php
namespace App\Http\Controllers;
use Event;
use App\Podcast;
use App\Events\PodcastWasPurchased;
use App\Http\Controllers\Controller;
class UserController extends Controller{
/**
* 显示指定用户属性
*
* @param int $userId
* @param int $podcastId
* @return Response
*/
public function purchasePodcast($userId, $podcastId)
{
$podcast = Podcast::findOrFail($podcastId);
// Purchase podcast logic...
Event::fire(new PodcastWasPurchased($podcast));
}
}
~~~
此外,你还可以使用全局的帮助函数`event`来触发事件:
~~~
event(new PodcastWasPurchased($podcast));
~~~
## 6、广播事件
在很多现代web应用中,web套接字被用于实现实时更新的用户接口。当一些数据在服务器上被更新,通常一条消息通过websocket连接被发送给客户端处理。
为帮助你构建这样的应用,Laravel让通过websocket连接广播事件变得简单。广播Laravel事件允许你在服务端和客户端JavaScript框架之间共享同一事件名。
### 6.1 配置
所有的事件广播配置选项都存放在`config/broadcasting.php`配置文件中。Laravel支持多种广播驱动:[Pusher](https://pusher.com/)、Redis以及一个服务于本地开发和调试的日志驱动。每一个驱动都有一个配置示例。
### 6.1.1 广播预备知识
事件广播需要以下两个依赖:
* Pusher: `pusher/pusher-php-server ~2.0`
* Redis: `predis/predis ~1.0`
### 6.1.2 队列预备知识
在开始介绍广播事件之前,还需要配置并运行一个[队列监听器](http://laravelacademy.org/post/222.html)。所有事件广播都通过队列任务来完成以便应用的响应时间不受影响。
### 6.2 将事件标记为广播
要告诉Laravel给定事件应该被广播,需要在事件类上实现`Illuminate\Contracts\Broadcasting\ShouldBroadcast`接口。`ShouldBroadcast`接口要求你实现一个方法:`broadcastOn`。该方法应该返回事件广播”频道“名称数组:
~~~
<?php
namespace App\Events;
use App\User;
use App\Events\Event;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
class ServerCreated extends Event implements ShouldBroadcast{
use SerializesModels;
public $user;
/**
* 创建新的事件实例
*
* @return void
*/
public function __construct(User $user)
{
$this->user = $user;
}
/**
* 获取事件广播频道
*
* @return array
*/
public function broadcastOn()
{
return ['user.'.$this->user->id];
}
}
~~~
然后,你只需要和正常一样[触发该事件](http://laravelacademy.org/post/198.html#firing-events),事件被触发后,一个[队列任务](http://laravelacademy.org/post/222.html)将通过指定广播驱动自动广播该事件。
### 6.3 广播数据
如果某个事件被广播,其所有的`public`属性都会按照事件负载自动序列化和广播,从而允许你从JavaScript中访问所有`public`数据,因此,举个例子,如果你的事件有一个单独的包含Eloquent模型的`$user`属性,广播负载定义如下:
~~~
{
"user": {
"id": 1,
"name": "Jonathan Banks"
...
}
}
~~~
然而,如果你希望对广播负载有更加细粒度的控制,可以添加`broadcastWith`方法到事件,该方法应该返回你想要通过事件广播的数组数据:
~~~
/**
* 获取广播数据
*
* @return array
*/
public function broadcastWith(){
return ['user' => $this->user->id];
}
~~~
### 6.4 消费事件广播
### 6.4.1 Pusher
你可以通过Pusher的JavaScript SDK方便地使用[Pusher](https://pusher.com/)驱动消费事件广播。例如,让我们从之前的例子中消费App\Events\ServerCreated事件:
~~~
this.pusher = new Pusher('pusher-key');
this.pusherChannel = this.pusher.subscribe('user.' + USER_ID);
this.pusherChannel.bind('App\\Events\\ServerCreated', function(message) {
console.log(message.user);
});
~~~
### 6.4.2 Redis
如果你在使用Redis广播,你将需要编写自己的Redis pub/sub消费者来接收消息并使用自己选择的websocket技术将其进行广播。例如,你可以选择使用使用Node编写的流行的[Socket.io](http://socket.io/)库。
使用Node库`socket.io`和`ioredis`,你可以快速编写事件广播发布所有广播事件:
~~~
var app = require('http').createServer(handler);
var io = require('socket.io')(app);
var Redis = require('ioredis');
var redis = new Redis();
app.listen(6001, function() {
console.log('Server is running!');});
function handler(req, res) {
res.writeHead(200);
res.end('');}
io.on('connection', function(socket) {
//
});
redis.psubscribe('*', function(err, count) {
//
});
redis.on('pmessage', function(subscribed, channel, message) {
message = JSON.parse(message);
~~~
});
## 7、事件订阅者
事件订阅者是指那些在类本身中订阅到多个事件的类,从而允许你在单个类中定义一些事件处理器。订阅者应该定义一个`subscribe`方法,该方法中传入一个事件分发器实例:
~~~
<?php
namespace App\Listeners;
class UserEventListener{
/**
* 处理用户登录事件
*/
public function onUserLogin($event) {}
/**
* 处理用户退出事件
*/
public function onUserLogout($event) {}
/**
* 为订阅者注册监听器
*
* @param Illuminate\Events\Dispatcher $events
* @return array
*/
public function subscribe($events)
{
$events->listen(
'App\Events\UserLoggedIn',
'App\Listeners\UserEventListener@onUserLogin'
);
$events->listen(
'App\Events\UserLoggedOut',
'App\Listeners\UserEventListener@onUserLogout'
);
}
}
~~~
### 7.1 注册一个事件订阅者
订阅者被定义后,可以通过事件分发器进行注册,你可以使用`EventServiceProvider`上的`$subcribe`属性来注册订阅者。例如,让我们添加`UserEventListener`:
~~~
<?php
namespace App\Providers;
use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider{
/**
* 事件监听器映射数组
*
* @var array
*/
protected $listen = [
//
];
/**
* 要注册的订阅者
*
* @var array
*/
protected $subscribe = [
'App\Listeners\UserEventListener',
];
}
~~~
服务 ―― 错误&日志
最后更新于:2022-04-01 04:11:09
服务 ―― 加密
最后更新于:2022-04-01 04:11:06
# 服务 —— 加密
## 1、配置
在使用Laravel的加密器之前,应该在`config/app.php`配置文件中设置key选项为32位随机字符串。如果这个值没有被设置,所有Laravel加密过的值都是不安全的。
## 2、基本使用
### 2.1 加密
你可以使用`Crypt和`AES-256-CBC`密码进行加密。此外,所有加密值都通过一个消息认证码(MAC)来检测对加密字符串的任何修改。
例如,我们可以使用`encrypt`方法加密`secret`属性并将其存储到Eloquent模型:
~~~
<?php
namespace App\Http\Controllers;
use Crypt;
use App\User;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class UserController extends Controller{
/**
* Store a secret message for the user.
*
* @param Request $request
* @param int $id
* @return Response
*/
public function storeSecret(Request $request, $id)
{
$user = User::findOrFail($id);
$user->fill([
'secret' => Crypt::encrypt($request->secret)
])->save();
}
}
~~~
### 2.2 解密
当然,你可以使用`Crypt`门面上的`decrypt`方法进行解密。如果该值不能被解密,例如MAC无效,将会抛出一个`Illuminate\Contracts\Encryption\DecryptException`异常:
~~~
use Illuminate\Contracts\Encryption\DecryptException;
try {
$decrypted = Crypt::decrypt($encryptedValue);
} catch (DecryptException $e) {
//
}
~~~
服务 ―― Laravel Elixir
最后更新于:2022-04-01 04:11:04
# 服务 —— Laravel Elixir
## 1、简介
Laravel Elixir预处理器,甚至是测试工具。使用方法链,Elixir允许你平滑的定义资源管道。例如:
~~~
elixir(function(mix) {
mix.sass('app.scss')
.coffee('app.coffee');
});
~~~
如果你曾经对如何开始Gulp编译感到困惑,那么你会爱上Laravel Elixir。然而,并不是强制要求在开发期间使用它。你可以自由选择使用任何前端资源管道工具,或者压根不使用。
## 2、安装 & 设置
### 2.1 安装Node
在开始Elixir之前,必须首先确保Node.js在机器上已经安装:
~~~
node -v
~~~
默认情况下,Laravel Homestead包含你需要的一切;然而,如果你不使用Vagrant,你也可以通过访问[Node的下载页面](http://nodejs.org/download/)轻松的安装Node。
### 2.2 Gulp
接下来,需要安装Gulp作为全局NPM包:
~~~
npm install --global gulp
~~~
### 2.3 Laravel Elixir
最后,在新安装的Laravel根目录下,你会发现有一个`package.json` 文件。该文件和`composer.json`一样,只不过是用来定义Node依赖而非PHP,你可以通过运行如下命令来安装需要的依赖:
~~~
npm install
~~~
如果你正在Windows系统上开发,需要在运行`npm install`命令时带上`--no-bin-links`:
~~~
npm install --no-bin-links
~~~
## 3、运行Elixir
Elixir基于Gulp,所以要运行Elixir命令你只需要在终端中运行`gulp`命令即可。添加`--production`标识到命令将会最小化CSS和JavaScript文件:
~~~
// Run all tasks...
gulp
// Run all tasks and minify all CSS and JavaScript...
gulp --production
~~~
### 3.1 监控前端资源改变
由于每次修改前端资源后都要运行`gulp`很不方便,可以使用`gulp watch`命令。该命令将会一直在终端运行并监控前端文件的改动。当改变发生时,新文件将会自动被编译:
~~~
gulp watch
~~~
## 4、处理CSS
项目根目录下的`gulpfile.js`文件包含了所有的Elixir任务。Elixir任务可以使用方法链的方式链接起来用于定义前端资源如何被编译。
### 4.1 Less
要将[Less](http://lesscss.org/)编译成CSS,可以使用`less`方法。less方法假定你的Less文件都放在`resources/assets/less`。默认情况下,本例中该任务会将编译后的CSS放到`public/css/app.css`:
~~~
elixir(function(mix) {
mix.less('app.less');
});
~~~
你还可以将多个Less文件编译成单个CSS文件。同样,该文件会被放到`public/css/app.css`:
~~~
elixir(function(mix) {
mix.less([
'app.less',
'controllers.less'
]);
});
~~~
如果你想要自定义编译后文件的输出位置,可以传递第二个参数到`less`方法:
~~~
elixir(function(mix) {
mix.less('app.less', 'public/stylesheets');
});
// Specifying a specific output filename...
elixir(function(mix) {
mix.less('app.less', 'public/stylesheets/style.css');
});
~~~
### 4.2 Sass
`sass`方法允许你将[Sass](http://sass-lang.com/)编译成CSS。假定你的Sass文件存放在`resources/assets/sass`,你可以像这样使用该方法:
~~~
elixir(function(mix) {
mix.sass('app.scss');
});
~~~
同样,和`less`方法一样,你可以将多个脚本编译成单个CSS文件,甚至自定义结果CSS的输出路径:
~~~
elixir(function(mix) {
mix.sass([
'app.scss',
'controllers.scss'
], 'public/assets/css');
});
~~~
### 4.3 原生CSS
如果你只想要将多个原生CSS样式文件合并到一个文件,可以使用`styles`方法。传递给该方法的路径相对于`resources/assets/css`目录,结果CSS被存放在`public/css/all.css`:
~~~
elixir(function(mix) {
mix.styles([
'normalize.css',
'main.css'
]);
});
~~~
当然,你还可以通过传递第二个参数到`styles`方法来输出结果文件到一个自定义路径:
~~~
elixir(function(mix) {
mix.styles([
'normalize.css',
'main.css'
], 'public/assets/css');});
~~~
### 4.4 源地图
默认源地图被启用,所以,对于每一个你编译过的文件都可以在同一目录下找到一个对应的`*.css.map`文件。这种匹配允许你在浏览器中调试时将编译过的样式选择器回溯到原来的Sass或Less。
如果你不想为CSS生成源地图,可以使用一个简单配置选项关闭它们:
~~~
elixir.config.sourcemaps = false;
elixir(function(mix) {
mix.sass('app.scss');
});
~~~
## 5、处理JavaScript
Elixir还提供了多个函数帮助你处理JavaScript文件,例如编译ECMAScript 6,CoffeeScript,Browserify,最小化以及简单连接原生JavaScript文件。
### 5.1 CoffeeScript
`coffee`方法用于将[CoffeeScript](http://coffeescript.org/)编译成原生JavaScript。该方法接收关联到`resources/assets/coffee`目录的CoffeeScript文件的一个字符串或数组并在`public/js`目录下生成单个`app.js`文件:
~~~
elixir(function(mix) {
mix.coffee(['app.coffee', 'controllers.coffee']);
});
~~~
### 5.2 Browserify
Elixir还提供了`browserify`方法,从而让你可以在浏览器中引入模块并使用EcmaScript 6。
该任务假定你的脚本都存放在`resources/assets/js`而且将结果文件存放到`public/js/bundle.js`:
~~~
elixir(function(mix) {
mix.browserify('main.js');
});
~~~
除了处理Partialify和Babelify,还可以安装并添加更多:
~~~
npm install vueify --save-dev
~~~
~~~
elixir.config.js.browserify.transformers.push({
name: 'vueify',
options: {}
});
elixir(function(mix) {
mix.browserify('main.js');
});
~~~
### 5.3 Babel
`babel`方法可用于将[EcmaScript 6和7](https://babeljs.io/docs/learn-es2015/)编译成原生JavaScript。该方法接收相对于`resources/assets/js`目录的文件数组,并在`public/js`目录下生成单个`all.js`:
~~~
elixir(function(mix) {
mix.babel([
'order.js',
'product.js'
]);});
~~~
要选择不同的输出路径,只需将目标路径作为第二个参数传递给该方法。处了Babel编译之外,`babel`和`mix.scripts()`的使用方法和功能差不多。
### 5.4 脚本
如果你有多个JavaScript文件想要编译成单个文件,可以使用`scripts`方法。
`scripts`方法假定所有路径相对于`resources/assets/js`目录,而且所有结果JavaScript默认存放在`public/js/all.js`:
~~~
elixir(function(mix) {
mix.scripts([
'jquery.js',
'app.js'
]);
});
~~~
如果你需要将多个脚本集合合并到不同的文件,需要多次调用`scripts`方法。该方法的第二个参数决定每个合并的结果文件名:
~~~
elixir(function(mix) {
mix.scripts(['app.js', 'controllers.js'], 'public/js/app.js')
.scripts(['forum.js', 'threads.js'], 'public/js/forum.js');
});
~~~
如果你需要将多个脚本合并到给定目录,可以使用`scriptsIn`方法。结果JavaScript会被存放到`public/js/all.js`:
~~~
elixir(function(mix) {
mix.scriptsIn('public/js/some/directory');
});
~~~
## 6、版本号刷新
很多开发者会给编译的前端资源添加时间戳或者唯一令牌后缀以强制浏览器加载最新版本而不是代码的缓存副本。Elixir可以使用`version`方法为你处理这种情况。
`version`方法接收相对于`public`目录的文件名,附加唯一hash到文件名,从而实现缓存刷新。例如,生成的文件名看上去是这样——`all-16d570a7.css`:
~~~
elixir(function(mix) {
mix.version('css/all.css');
});
~~~
生成版本文件后,可以在视图中使用Elixir全局的PHP帮助函数`elixir`方法来加载相应的带hash值的前端资源,`elixir`函数会自动判断hash文件名:
~~~
<link rel="stylesheet" href="{{ elixir('css/all.css') }}">
~~~
### 6.1 给多个文件加上版本号
你可以传递一个数组到`version`方法来为多个文件添加版本号:
~~~
elixir(function(mix) {
mix.version(['css/all.css', 'js/app.js']);
});
~~~
一旦文件被加上版本号,就可以使用帮助函数`elixir`来生成指向该hash文件的链接。记住,你只需要传递没有hash值的文件名到`elixir`方法。该帮助函数使用未加hash值的文件名来判断文件当前的hash版本:
~~~
<link rel="stylesheet" href="{{ elixir('css/all.css') }}">
<script src="{{ elixir('js/app.js') }}"></script>
~~~
## 7、调用存在的Gulp任务
如果你需要从Elixir调用已存在的Gulp任务,可以使用`task`方法。例如,假定你有一个调用时只是简单说几句话的Gulp任务:
~~~
gulp.task('speak', function() {
var message = 'Tea...Earl Grey...Hot';
gulp.src('').pipe(shell('say ' + message));
});
~~~
如果你想要从Elixir中调用该任务,使用`mix.task`方法并传递任务名作为该方法的唯一参数:
~~~
elixir(function(mix) {
mix.task('speak');
});
~~~
### 7.1 自定义监控者
如果你需要注册一个监控器在每一次文件修改时都运行自定义任务,传递一个正则表达式作为`task`方法的第二个参数:
~~~
elixir(function(mix) {
mix.task('speak', 'app/**/*.php');});
~~~
## 8、编写Elixir扩展
如果你需要比Elixir的`task`方法所提供的更加灵活的功能,可以创建自定义的Elixir扩展。Elixir扩展允许你传递参数到自定义任务,例如,你可以像这样编写一个扩展:
~~~
// File: elixir-extensions.js
var gulp = require('gulp');
var shell = require('gulp-shell');
var Elixir = require('laravel-elixir');
var Task = Elixir.Task;
Elixir.extend('speak', function(message) {
new Task('speak', function() {
return gulp.src('').pipe(shell('say ' + message));
});
});
// mix.speak('Hello World');
~~~
就是这样简单!注意你的特定Gulp逻辑应该放到闭包函数里作为第二个参数传递给`Task`构造器。你可以将其放在`Gulpfile`顶端,或者将其解析到自定义的任务文件。例如,如果你将扩展放在`elixir-extensions.js`,可以在主`Gulpfile`中像这样引入该文件:
~~~
// File: Gulpfile.js
var elixir = require('laravel-elixir');
require('./elixir-extensions')
elixir(function(mix) {
mix.speak('Tea, Earl Grey, Hot');
});
~~~
### 8.1 自定义监控器
如果你想要自定义任务在运行`gulp watch`的时候被触发,可以注册一个监控器:
~~~
new Task('speak', function() {
return gulp.src('').pipe(shell('say ' + message));
}).watch('./app/**');
~~~
服务 ―― 集合
最后更新于:2022-04-01 04:11:02
# 服务 —— 集合
## 1、简介
`Illuminate\Support\Collection`类为处理数组数据提供了平滑、方便的封装。例如,查看下面的代码,我们使用帮助函数`collect`创建一个新的集合实例,为每一个元素运行`strtoupper`函数,然后移除所有空元素:
~~~
$collection = collect(['taylor', 'abigail', null])->map(function ($name) {
return strtoupper($name);
})->reject(function ($name) {
return empty($name);
});
~~~
正如你所看到的,`Collection`类允许你使用方法链对底层数组执行匹配和减少操作,通常,没个`Collection`方法都会返回一个新的`Collection`实例。
## 2、创建集合
正如上面所提到的,帮助函数`collect`为给定数组返回一个新的`Illuminate\Support\Collection`实例,所以,创建集合很简单:
~~~
$collection = collect([1, 2, 3]);
~~~
默认情况下,Eloquent模型的集合总是返回`Collection`实例,此外,不管是在何处,只要方法都可以自由使用`Collection`类。
## 3、集合方法列表
本文档接下来的部分我们将会讨论`Collection`类上每一个有效的方法,所有这些方法都可以以方法链的方式平滑的操作底层数组。此外,几乎每个方法返回一个新的`Collection`实例,允许你在必要的时候保持原来的集合备份。
### all()
`all`方法简单返回集合表示的底层数组:
~~~
collect([1, 2, 3])->all();
// [1, 2, 3]
~~~
### chunk()
`chunk`方法将一个集合分割成多个小尺寸的小集合:
~~~
$collection = collect([1, 2, 3, 4, 5, 6, 7]);
$chunks = $collection->chunk(4);
$chunks->toArray();
// [[1, 2, 3, 4], [5, 6, 7]]
~~~
当处理栅栏系统如[Bootstrap](http://getbootstrap.com/css/#grid)时该方法在[视图](http://laravelacademy.org/post/76.html)中尤其有用,建设你有一个想要显示在栅栏中的[Eloquent](http://laravelacademy.org/post/138.html)模型集合:
~~~
@foreach ($products->chunk(3) as $chunk)
<div class="row">
@foreach ($chunk as $product)
<div class="col-xs-4">{{ $product->name }}</div>
@endforeach
</div>
@endforeach
~~~
### collapse()
`collapse`方法将一个多维数组集合收缩成一个一维数组:
~~~
$collection = collect([[1, 2, 3], [4, 5, 6], [7, 8, 9]]);
$collapsed = $collection->collapse();
$collapsed->all();
// [1, 2, 3, 4, 5, 6, 7, 8, 9]
~~~
### contains()
`contains`方法判断集合是否包含一个给定项:
~~~
$collection = collect(['name' => 'Desk', 'price' => 100]);
$collection->contains('Desk');
// true
$collection->contains('New York');
// false
~~~
你还可以传递一个键值对到`contains`方法,这将会判断给定键值对是否存在于集合中:
~~~
$collection = collect([
['product' => 'Desk', 'price' => 200],
['product' => 'Chair', 'price' => 100],
]);
$collection->contains('product', 'Bookcase');
// false
~~~
最后,你还可以传递一个回调到`contains`方法来执行自己的真实测试:
~~~
$collection = collect([1, 2, 3, 4, 5]);
$collection->contains(function ($key, $value) {
return $value > 5;
});
// false
~~~
### count()
`count`方法返回集合中所有项的数目:
~~~
$collection = collect([1, 2, 3, 4]);
$collection->count();
// 4
~~~
### diff()
`diff`方法将集合和另一个集合或原生PHP数组作比较:
~~~
$collection = collect([1, 2, 3, 4, 5]);
$diff = $collection->diff([2, 4, 6, 8]);
$diff->all();
// [1, 3, 5]
~~~
### each()
`each`方法迭代集合中的数据项并传递每个数据项到给定回调:
~~~
$collection = $collection->each(function ($item, $key) {
//
});
~~~
回调返回`false`将会终止循环:
~~~
$collection = $collection->each(function ($item, $key) {
if (/* some condition */) {
return false;
}
});
~~~
### filter()
`filter`方法通过给定回调过滤集合,只有通过给定测试的数据项才会保留下来:
~~~
$collection = collect([1, 2, 3, 4]);
$filtered = $collection->filter(function ($item) {
return $item > 2;
});
$filtered->all();
// [3, 4]
~~~
和`filter`相反的方法是[`reject`](http://laravelacademy.org/post/178.html#ipt_kb_toc_178_35)。
### first()
`first`方法返回通过测试集合的第一个元素:
~~~
collect([1, 2, 3, 4])->first(function ($key, $value) {
return $value > 2;
});
// 3
~~~
你还可以调用不带参数的`first`方法来获取集合的第一个元素,如果集合是空的,返回null:
~~~
collect([1, 2, 3, 4])->first();
// 1
~~~
### flatten()
`flatten`方法将多维度的集合变成一维的:
~~~
$collection = collect(['name' => 'taylor', 'languages' => ['php', 'javascript']]);
$flattened = $collection->flatten();
$flattened->all();
// ['taylor', 'php', 'javascript'];
~~~
### flip()
`flip`方法将集合的键值做交换:
~~~
$collection = collect(['name' => 'taylor', 'framework' => 'laravel']);
$flipped = $collection->flip();
$flipped->all();
// ['taylor' => 'name', 'laravel' => 'framework']
~~~
### forget()
`forget`方法通过键从集合中移除数据项:
~~~
$collection = collect(['name' => 'taylor', 'framework' => 'laravel']);
$collection->forget('name');
$collection->all();
// [framework' => 'laravel']
~~~
注意:不同于大多数的集合方法,`forget`不返回新的修改过的集合;它只修改所调用的集合。
### forPage()
`forPage`方法返回新的包含给定页数数据项的集合:
~~~
$collection = collect([1, 2, 3, 4, 5, 6, 7, 8, 9])->forPage(2, 3);
$collection->all();
// [4, 5, 6]
~~~
该方法需要传入页数和每页显示数目参数。
### get()
`get`方法返回给定键的数据项,如果不存在,返回null:
~~~
$collection = collect(['name' => 'taylor', 'framework' => 'laravel']);
$value = $collection->get('name');
// taylor
~~~
你可以选择传递默认值作为第二个参数:
~~~
$collection = collect(['name' => 'taylor', 'framework' => 'laravel']);
$value = $collection->get('foo', 'default-value');
// default-value
~~~
你甚至可以传递回调作为默认值,如果给定键不存在的话回调的结果将会返回:
~~~
$collection->get('email', function () {
return 'default-value';});
// default-value
~~~
### groupBy()
`groupBy`方法通过给定键分组集合数据项:
~~~
$collection = collect([
['account_id' => 'account-x10', 'product' => 'Chair'],
['account_id' => 'account-x10', 'product' => 'Bookcase'],
['account_id' => 'account-x11', 'product' => 'Desk'],
]);
$grouped = $collection->groupBy('account_id');
$grouped->toArray();
/*
[
'account-x10' => [
['account_id' => 'account-x10', 'product' => 'Chair'],
['account_id' => 'account-x10', 'product' => 'Bookcase'],
],
'account-x11' => [
['account_id' => 'account-x11', 'product' => 'Desk'],
],
]
*/
~~~
除了传递字符串key,还可以传递一个回调,回调应该返回分组后的值:
~~~
$grouped = $collection->groupBy(function ($item, $key) {
return substr($item['account_id'], -3);
});
$grouped->toArray();
/*
[
'x10' => [
['account_id' => 'account-x10', 'product' => 'Chair'],
['account_id' => 'account-x10', 'product' => 'Bookcase'],
],
'x11' => [
['account_id' => 'account-x11', 'product' => 'Desk'],
],
]
*/
~~~
### has()
`has`方法判断给定键是否在集合中存在:
~~~
$collection = collect(['account_id' => 1, 'product' => 'Desk']);
$collection->has('email');
// false
~~~
### implode()
`implode`方法连接集合中的数据项。其参数取决于集合中数据项的类型。
如果集合包含数组或对象,应该传递你想要连接的属性键,以及你想要放在值之间的 “粘合”字符串:
~~~
$collection = collect([
['account_id' => 1, 'product' => 'Desk'],
['account_id' => 2, 'product' => 'Chair'],
]);
$collection->implode('product', ', ');
// Desk, Chair
~~~
如果集合包含简单的字符串或数值,只需要传递“粘合”字符串作为唯一参数到该方法:
~~~
collect([1, 2, 3, 4, 5])->implode('-');
// '1-2-3-4-5'
~~~
### intersect()
`intersect`方法返回两个集合的交集:
~~~
$collection = collect(['Desk', 'Sofa', 'Chair']);
$intersect = $collection->intersect(['Desk', 'Chair', 'Bookcase']);
$intersect->all();
// [0 => 'Desk', 2 => 'Chair']
~~~
正如你所看到的,结果集合只保持原来集合的键。
### isEmpty()
如果集合为空的话`isEmpty`方法返回`true`;否则返回`false`:
~~~
collect([])->isEmpty();
// true
~~~
### keyBy()
将指定键的值作为集合的键:
~~~
$collection = collect([
['product_id' => 'prod-100', 'name' => 'desk'],
['product_id' => 'prod-200', 'name' => 'chair'],
]);
$keyed = $collection->keyBy('product_id');
$keyed->all();
/*
[
'prod-100' => ['product_id' => 'prod-100', 'name' => 'Desk'],
'prod-200' => ['product_id' => 'prod-200', 'name' => 'Chair'],
]
*/
~~~
如果多个数据项有同一个键,只有最后一个会出现在新的集合中。
你可以传递自己的回调,将会返回经过处理的键的值作为新的键:
~~~
$keyed = $collection->keyBy(function ($item) {
return strtoupper($item['product_id']);
});
$keyed->all();
/*
[
'PROD-100' => ['product_id' => 'prod-100', 'name' => 'Desk'],
'PROD-200' => ['product_id' => 'prod-200', 'name' => 'Chair'],
]
*/
~~~
### keys()
`keys`方法返回所有集合的键:
~~~
$collection = collect([
'prod-100' => ['product_id' => 'prod-100', 'name' => 'Desk'],
'prod-200' => ['product_id' => 'prod-200', 'name' => 'Chair'],
]);
$keys = $collection->keys();
$keys->all();
// ['prod-100', 'prod-200']
~~~
### last()
`last`方法返回通过测试的集合的最后一个元素:
~~~
collect([1, 2, 3, 4])->last(function ($key, $value) {
return $value < 3;
});
// 2
~~~
还可以调用无参的`last`方法来获取集合的最后一个元素。如果集合为空。返回null:
~~~
collect([1, 2, 3, 4])->last();
// 4
~~~
### map()
`map`方法遍历集合并传递每个值给给定回调。该回调可以修改数据项并返回,从而生成一个新的经过修改的集合:
~~~
$collection = collect([1, 2, 3, 4, 5]);
$multiplied = $collection->map(function ($item, $key) {
return $item * 2;
});
$multiplied->all();
// [2, 4, 6, 8, 10]
~~~
注意:和大多数集合方法一样,`map`返回新的集合实例;它并不修改所调用的实例。如果你想要改变原来的集合,使用`[`transform`](http://laravelacademy.org/post/178.html#ipt_kb_toc_178_49)`方法。
### merge()
`merge`方法合并给定数组到集合。该数组中的任何字符串键匹配集合中的字符串键的将会重写集合中的值:
~~~
$collection = collect(['product_id' => 1, 'name' => 'Desk']);
$merged = $collection->merge(['price' => 100, 'discount' => false]);
$merged->all();
// ['product_id' => 1, 'name' => 'Desk', 'price' => 100, 'discount' => false]
~~~
如果给定数组的键是数字,数组的值将会附加到集合后面:
~~~
$collection = collect(['Desk', 'Chair']);
$merged = $collection->merge(['Bookcase', 'Door']);
$merged->all();
// ['Desk', 'Chair', 'Bookcase', 'Door']
~~~
### pluck()
`pluck`方法为给定键获取所有集合值:
~~~
$collection = collect([
['product_id' => 'prod-100', 'name' => 'Desk'],
['product_id' => 'prod-200', 'name' => 'Chair'],
]);
$plucked = $collection->pluck('name');
$plucked->all();
// ['Desk', 'Chair']
~~~
还可以指定你想要结果集合如何设置键:
~~~
$plucked = $collection->pluck('name', 'product_id');
$plucked->all();
// ['prod-100' => 'Desk', 'prod-200' => 'Chair']
~~~
### pop()
`pop`方法移除并返回集合中最后面的数据项:
~~~
$collection = collect([1, 2, 3, 4, 5]);
$collection->pop();
// 5
$collection->all();
// [1, 2, 3, 4]
~~~
### prepend()
`prepend`方法添加数据项到集合开头:
~~~
$collection = collect([1, 2, 3, 4, 5]);
$collection->prepend(0);
$collection->all();
// [0, 1, 2, 3, 4, 5]
~~~
### pull()
`pull`方法通过键从集合中移除并返回数据项:
~~~
$collection = collect(['product_id' => 'prod-100', 'name' => 'Desk']);
$collection->pull('name');
// 'Desk'
$collection->all();
// ['product_id' => 'prod-100']
~~~
### push()
`push`方法附加数据项到集合结尾:
~~~
$collection = collect([1, 2, 3, 4]);
$collection->push(5);
$collection->all();
// [1, 2, 3, 4, 5]
~~~
### put()
`put`方法在集合中设置给定键和值:
~~~
$collection = collect(['product_id' => 1, 'name' => 'Desk']);
$collection->put('price', 100);
$collection->all();
// ['product_id' => 1, 'name' => 'Desk', 'price' => 100]
~~~
### random()
`random` 方法从集合中返回随机数据项:
~~~
$collection = collect([1, 2, 3, 4, 5]);
$collection->random();
// 4 - (retrieved randomly)
~~~
你可以传递一个整型数据到`random`函数,如果该整型数值大于1,将会返回一个集合:
~~~
$random = $collection->random(3);
$random->all();
// [2, 4, 5] - (retrieved randomly)
~~~
### reduce()
`reduce` 方法用于减少集合到单个值,传递每个迭代结果到随后的迭代:
~~~
$collection = collect([1, 2, 3]);
$total = $collection->reduce(function ($carry, $item) {
return $carry + $item;
});
// 6
~~~
在第一次迭代时`$carry`的值是null;然而,你可以通过传递第二个参数到`reduce`来指定其初始值:
~~~
$collection->reduce(function ($carry, $item) {
return $carry + $item;
}, 4);
// 10
~~~
### reject()
reject方法使用给定回调过滤集合,该回调应该为所有它想要从结果集合中移除的数据项返回true:
~~~
$collection = collect([1, 2, 3, 4]);
$filtered = $collection->reject(function ($item) {
return $item > 2;
});
$filtered->all();
// [1, 2]
~~~
和`reduce`方法相对的方法是[filter](http://laravelacademy.org/post/178.html#ipt_kb_toc_178_10)方法。
### reverse()
`reverse`方法将集合数据项的顺序颠倒:
~~~
$collection = collect([1, 2, 3, 4, 5]);
$reversed = $collection->reverse();
$reversed->all();
// [5, 4, 3, 2, 1]
~~~
### search()
`search`方法为给定值查询集合,如果找到的话返回对应的键,如果没找到,则返回`false`:
~~~
$collection = collect([2, 4, 6, 8]);
$collection->search(4);
// 1
~~~
上面的搜索使用的是松散比较,要使用严格比较,传递`true`作为第二个参数到该方法:
~~~
$collection->search('4', true);
// false
~~~
此外,你还可以传递自己的回调来搜索通过测试的第一个数据项:
~~~
$collection->search(function ($item, $key) {
return $item > 5;});
// 2
~~~
### shift()
`shift`方法从集合中移除并返回第一个数据项:
~~~
$collection = collect([1, 2, 3, 4, 5]);
$collection->shift();
// 1
$collection->all();
// [2, 3, 4, 5]
~~~
### shuffle()
`shuffle`方法随机打乱集合中的数据项:
~~~
$collection = collect([1, 2, 3, 4, 5]);
$shuffled = $collection->shuffle();
$shuffled->all();
// [3, 2, 5, 1, 4] // (generated randomly)
~~~
### slice()
`slice`方法从给定索开始返回集合的一个切片:
~~~
$collection = collect([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
$slice = $collection->slice(4);
$slice->all();
// [5, 6, 7, 8, 9, 10]
~~~
如果你想要限制返回切片的尺寸,将尺寸值作为第二个参数传递到该方法:
~~~
$slice = $collection->slice(4, 2);
$slice->all();
// [5, 6]
~~~
返回的切片有新的、数字化索引的键,如果你想要保持原有的键,可以传递第三个参数true到该方法。
### sort()
`sort`方法对集合进行排序:
~~~
$collection = collect([5, 3, 1, 2, 4]);
$sorted = $collection->sort();
$sorted->values()->all();
// [1, 2, 3, 4, 5]
~~~
排序后的集合保持原来的数组键,在本例中我们使用[`values`](http://laravelacademy.org/post/178.html#ipt_kb_toc_178_51) 方法重置键为连续编号索引。
要为嵌套集合和对象排序,查看`[sortBy](http://laravelacademy.org/post/178.html#ipt_kb_toc_178_42)`和`[sortByDesc](http://laravelacademy.org/post/178.html#ipt_kb_toc_178_43)`方法。
如果你需要更加高级的排序,你可以使用自己的算法传递一个回调给`sort`方法。参考PHP官方文档关于 `[usort](http://php.net/manual/en/function.usort.php#refsect1-function.usort-parameters)`的说明,sort方法底层正是调用了该方法。
### sortBy()
`sortBy`方法通过给定键对集合进行排序:
~~~
$collection = collect([
['name' => 'Desk', 'price' => 200],
['name' => 'Chair', 'price' => 100],
['name' => 'Bookcase', 'price' => 150],
]);
$sorted = $collection->sortBy('price');
$sorted->values()->all();
/*
[
['name' => 'Chair', 'price' => 100],
['name' => 'Bookcase', 'price' => 150],
['name' => 'Desk', 'price' => 200],
]
*/
~~~
排序后的集合保持原有数组索引,在本例中,使用`[values](http://laravelacademy.org/post/178.html#ipt_kb_toc_178_51)`方法重置键为连续索引。
你还可以传递自己的回调来判断如何排序集合的值:
~~~
$collection = collect([
['name' => 'Desk', 'colors' => ['Black', 'Mahogany']],
['name' => 'Chair', 'colors' => ['Black']],
['name' => 'Bookcase', 'colors' => ['Red', 'Beige', 'Brown']],
]);
$sorted = $collection->sortBy(function ($product, $key) {
return count($product['colors']);
});
$sorted->values()->all();
/*
[
['name' => 'Chair', 'colors' => ['Black']],
['name' => 'Desk', 'colors' => ['Black', 'Mahogany']],
['name' => 'Bookcase', 'colors' => ['Red', 'Beige', 'Brown']],
]
*/
~~~
### sortByDesc()
该方法和 `[sortBy](http://laravelacademy.org/post/178.html#ipt_kb_toc_178_42)`用法相同,不同之处在于按照相反顺序进行排序。
### splice()
`splice`方法在从给定位置开始移除并返回数据项切片:
~~~
$collection = collect([1, 2, 3, 4, 5]);
$chunk = $collection->splice(2);
$chunk->all();
// [3, 4, 5]
$collection->all();
// [1, 2]
~~~
你可以传递参数来限制返回组块的大小:
~~~
$collection = collect([1, 2, 3, 4, 5]);
$chunk = $collection->splice(2, 1);
$chunk->all();
// [3]
$collection->all();
// [1, 2, 4, 5]
~~~
此外,你可以传递第三个参数来包含新的数据项来替代从集合中移除的数据项:
~~~
$collection = collect([1, 2, 3, 4, 5]);
$chunk = $collection->splice(2, 1, [10, 11]);
$chunk->all();
// [3]
$collection->all();
// [1, 2, 10, 11, 4, 5]
~~~
### sum()
`sum`方法返回集合中所有数据项的和:
~~~
collect([1, 2, 3, 4, 5])->sum();
// 15
~~~
如果集合包含嵌套数组或对象,应该传递一个键用于判断对哪些值进行求和运算:
~~~
$collection = collect([
['name' => 'JavaScript: The Good Parts', 'pages' => 176],
['name' => 'JavaScript: The Definitive Guide', 'pages' => 1096],
]);
$collection->sum('pages');
// 1272
~~~
此外,你还可以传递自己的回调来判断对哪些值进行求和:
~~~
$collection = collect([
['name' => 'Chair', 'colors' => ['Black']],
['name' => 'Desk', 'colors' => ['Black', 'Mahogany']],
['name' => 'Bookcase', 'colors' => ['Red', 'Beige', 'Brown']],
]);
$collection->sum(function ($product) {
return count($product['colors']);
});
// 6
~~~
### take()
`take`方法使用指定数目的数据项返回一个新的集合:
~~~
$collection = collect([0, 1, 2, 3, 4, 5]);
$chunk = $collection->take(3);
$chunk->all();
// [0, 1, 2]
~~~
你还可以传递负数从集合末尾开始获取指定数目的数据项:
~~~
$collection = collect([0, 1, 2, 3, 4, 5]);
$chunk = $collection->take(-2);
$chunk->all();
// [4, 5]
~~~
### toArray()
`toArray`方法将集合转化为一个原生的PHP数组。如果集合的值是Eloquent模型,该模型也会被转化为数组:
~~~
$collection = collect(['name' => 'Desk', 'price' => 200]);
$collection->toArray();
/*
[
['name' => 'Desk', 'price' => 200],
]
*/
~~~
注意:`toArray`还将所有嵌套对象转化为数组。如果你想要获取底层数组,使用`all`方法。
### toJson()
`toJson`方法将集合转化为JSON:
~~~
$collection = collect(['name' => 'Desk', 'price' => 200]);
$collection->toJson();
// '{"name":"Desk","price":200}'
~~~
### transform()
`transform`方法迭代集合并对集合中每个数据项调用给定回调。集合中的数据项将会被替代成从回调中返回的值:
~~~
$collection = collect([1, 2, 3, 4, 5]);
$collection->transform(function ($item, $key) {
return $item * 2;
});
$collection->all();
// [2, 4, 6, 8, 10]
~~~
注意:不同于大多数其它集合方法,`transform`修改集合本身,如果你想要创建一个新的集合,使用[map](http://laravelacademy.org/post/178.html#ipt_kb_toc_178_25)方法。
### unique()
`unique`方法返回集合中所有的唯一数据项:
~~~
$collection = collect([1, 1, 2, 2, 3, 4, 2]);
$unique = $collection->unique();
$unique->values()->all();
// [1, 2, 3, 4]
~~~
返回的集合保持原来的数组键,在本例中我们使用[values](http://laravelacademy.org/post/178.html#ipt_kb_toc_178_51)方法重置这些键为连续的数字索引。
处理嵌套数组或对象时,可以指定用于判断唯一的键:
~~~
$collection = collect([
['name' => 'iPhone 6', 'brand' => 'Apple', 'type' => 'phone'],
['name' => 'iPhone 5', 'brand' => 'Apple', 'type' => 'phone'],
['name' => 'Apple Watch', 'brand' => 'Apple', 'type' => 'watch'],
['name' => 'Galaxy S6', 'brand' => 'Samsung', 'type' => 'phone'],
['name' => 'Galaxy Gear', 'brand' => 'Samsung', 'type' => 'watch'],
]);
$unique = $collection->unique('brand');
$unique->values()->all();
/*
[
['name' => 'iPhone 6', 'brand' => 'Apple', 'type' => 'phone'],
['name' => 'Galaxy S6', 'brand' => 'Samsung', 'type' => 'phone'],
]
*/
~~~
你还可以指定自己的回调用于判断数据项唯一性:
~~~
$unique = $collection->unique(function ($item) {
return $item['brand'].$item['type'];
});
$unique->values()->all();
/*
[
['name' => 'iPhone 6', 'brand' => 'Apple', 'type' => 'phone'],
['name' => 'Apple Watch', 'brand' => 'Apple', 'type' => 'watch'],
['name' => 'Galaxy S6', 'brand' => 'Samsung', 'type' => 'phone'],
['name' => 'Galaxy Gear', 'brand' => 'Samsung', 'type' => 'watch'],
]
*/
~~~
### values()
`values`方法使用重置为连续整型数字的键返回新的集合:
~~~
$collection = collect([
10 => ['product' => 'Desk', 'price' => 200],
11 => ['product' => 'Desk', 'price' => 200]
]);
$values = $collection->values();
$values->all();
/*
[
0 => ['product' => 'Desk', 'price' => 200],
1 => ['product' => 'Desk', 'price' => 200],
]
*/
~~~
### where()
`where`方法通过给定键值对过滤集合:
~~~
$collection = collect([
['product' => 'Desk', 'price' => 200],
['product' => 'Chair', 'price' => 100],
['product' => 'Bookcase', 'price' => 150],
['product' => 'Door', 'price' => 100],
]);
$filtered = $collection->where('price', 100);
$filtered->all();
/*
[
['product' => 'Chair', 'price' => 100],
['product' => 'Door', 'price' => 100],
]
*/
~~~
检查数据项值时`where`方法使用严格条件约束。使用[whereLoose](http://laravelacademy.org/post/178.html#ipt_kb_toc_178_53)方法过滤松散约束。
### whereLoose()
该方法和`where`使用方法相同,不同之处在于`whereLoose`在比较值的时候使用松散约束。
### zip()
`zip`方法在于集合的值相应的索引处合并给定数组的值:
~~~
$collection = collect(['Chair', 'Desk']);
$zipped = $collection->zip([100, 200]);
$zipped->all();
// [['Chair', 100], ['Desk', 200]]
~~~
服务 ―― 缓存
最后更新于:2022-04-01 04:10:59
# 服务 —— 缓存
## 1、配置
Laravel为不同的缓存系统提供了统一的API。缓存配置位于`config/cache.php`。在该文件中你可以指定在应用中默认使用哪个缓存驱动。Laravel目前支持流行的缓存后端如[Memcached](http://memcached.org/)和[Redis](http://redis.io/)等。
缓存配置文件还包含其他文档化的选项,确保仔细阅读这些选项。默认情况下,Laravel被配置成使用文件缓存,这会将序列化数据和缓存对象存储到文件系统。对大型应用,建议使用内存缓存如Memcached或APC,你甚至可以为同一驱动配置多个缓存配置。
### 1.1 缓存预备知识
### 1.1.1 数据库
使用`database`缓存驱动时,你需要设置一张表包含缓存缓存项。下面是该表的`Schema`声明:
~~~
Schema::create('cache', function($table) {
$table->string('key')->unique();
$table->text('value');
$table->integer('expiration');
});
~~~
### 1.1.2 Memcached
使用Memcached缓存要求安装了[Memcached PECL 包](http://pecl.php.net/package/memcached),即PHP Memcached扩展。
[Memcached::addServer](http://php.net/manual/en/memcached.addserver.php)默认配置使用TCP/IP协议:
~~~
'memcached' => [
[
'host' => '127.0.0.1',
'port' => 11211,
'weight' => 100
],
],
~~~
你还可以设置`host` 选项为UNIX socket路径,如果你这样做,`port`选项应该置为0:
~~~
'memcached' => [
[
'host' => '/var/run/memcached/memcached.sock',
'port' => 0,
'weight' => 100
],
],
~~~
### 1.1.3 Redis
使用Laravel的Redis缓存之前,你需要通过Composer安装`predis/predis`包(~1.0)。
了解更多关于Redis的配置,查看[Larave的Redis文档](http://laravelacademy.org/post/228.html)。
## 2、缓存使用
### 2.1 获取缓存实例
`Illuminate\Contracts\Cache\Factory`和`Illuminate\Contracts\Cache\Repository`[契约](http://laravelacademy.org/post/95.html)提供了访问Laravel的缓存服务的方法。`Factory`契约提供了所有访问应用定义的缓存驱动的方法。`Repository`契约通常是应用中`cache`配置文件中指定的默认缓存驱动的一个实现。
然而,你还可以使用`Cache`[门面](http://laravelacademy.org/post/97.html),这也是我们在整个文档中使用的方式,`Cache`门面提供了简单方便的方式对底层Laravel缓存契约实现进行访问。
例如,让我们在控制器中导入`Cache`门面:
~~~
<?php
namespace App\Http\Controllers;
use Cache;
use Illuminate\Routing\Controller;
class UserController extends Controller{
/**
* 显示应用所有用户列表
*
* @return Response
*/
public function index()
{
$value = Cache::get('key');
//
}
}
~~~
### 2.1.1 访问多个缓存存储
使用`Cache`门面,你可以使用`store`方法访问不同的缓存存储器,传入`store`方法的键就是cache配置文件中`stores`配置数组里列出的相应的存储器:
~~~
$value = Cache::store('file')->get('foo');
Cache::store('redis')->put('bar', 'baz', 10);
~~~
### 2.2 从缓存中获取数据
`Cache`门面的`get`方法用于从缓存中获取缓存项,如果缓存项不存在,返回null。如果需要的话你可以传递第二个参数到`get`方法指定缓存项不存在时返回的自定义默认值:
~~~
$value = Cache::get('key');
$value = Cache::get('key', 'default');
~~~
你甚至可以传递一个闭包作为默认值,如果缓存项不存在的话闭包的结果将会被返回。传递闭包允许你可以从数据库或其它外部服务获取默认值:
~~~
$value = Cache::get('key', function() {
return DB::table(...)->get();
});
~~~
### 2.2.1 检查缓存项是否存在
`has`方法用于判断缓存项是否存在:
~~~
if (Cache::has('key')) {
//
}
~~~
### 2.2.2 数值增加/减少
`increment`和`decrement`方法可用于调整缓存中的整型数值。这两个方法都可以接收第二个参数来指明缓存项数值增加和减少的数目:
~~~
Cache::increment('key');
Cache::increment('key', $amount);
Cache::decrement('key');
Cache::decrement('key', $amount);
~~~
### 2.2.3 获取或更新
有时候你可能想要获取缓存项,但如果请求的缓存项不存在时给它存储一个默认值。例如,你可能想要从缓存中获取所有用户,或者如果它们不存在的话,从数据库获取它们并将其添加到缓存中,你可以通过使用`Cache::remember`方法实现:
~~~
$value = Cache::remember('users', $minutes, function() {
return DB::table('users')->get();});
~~~
如果缓存项不存在,传递给`remember`方法的闭包被执行并且将结果存放到缓存中。
你还可以联合`remember`和`forever`方法:
~~~
$value = Cache::rememberForever('users', function() {
return DB::table('users')->get();});
~~~
### 2.2.4 获取并删除
如果你需要从缓存中获取缓存项然后删除,你可以使用`pull`方法,和`get`方法一样,如果缓存项不存在的话返回null:
~~~
$value = Cache::pull('key');
~~~
### 2.3 存储缓存项到缓存
你可以使用`Cache` 门面上的`put`方法在缓存中存储缓存项。当你在缓存中存储缓存项的时候,你需要指定数据被缓存的时间(分钟数):
~~~
Cache::put('key', 'value', $minutes);
~~~
除了传递缓存项失效时间,你还可以传递一个代表缓存项有效时间的PHP `Datetime`实例:
~~~
$expiresAt = Carbon::now()->addMinutes(10);
Cache::put('key', 'value', $expiresAt);
~~~
`add`方法只会在缓存项不存在的情况下添加缓存项到缓存,如果缓存项被添加到缓存返回`true`,否则,返回`false`:
~~~
Cache::add('key', 'value', $minutes);
~~~
`forever`方法用于持久化存储缓存项到缓存,这些值必须通过`forget`方法手动从缓存中移除:
~~~
Cache::forever('key', 'value');
~~~
### 2.4 从缓存中移除数据
你可以使用`Cache`门面上的`forget`方法从缓存中移除缓存项:
~~~
Cache::forget('key');
~~~
## 3、添加自定义缓存驱动
要使用自定义驱动扩展Laravel缓存,我们使用`Cache`门面的`extend`方法,该方法用于绑定定义驱动解析器到管理器,通常,这可以在[服务提供者](http://laravelacademy.org/post/91.html)中完成。
例如,要注册一个新的命名为“mongo”的缓存驱动:
~~~
<?php
namespace App\Providers;
use Cache;
use App\Extensions\MongoStore;
use Illuminate\Support\ServiceProvider;
class CacheServiceProvider extends ServiceProvider{
/**
* Perform post-registration booting of services.
*
* @return void
*/
public function boot()
{
Cache::extend('mongo', function($app) {
return Cache::repository(new MongoStore);
});
}
/**
* Register bindings in the container.
*
* @return void
*/
public function register()
{
//
}
}
~~~
传递给`extend`方法的第一个参数是驱动名称。该值对应配置文件`config/cache.php`中的`driver`选项。第二个参数是返回`Illuminate\Cache\Repository`实例的闭包。该闭包中被传入一个`$app`实例,也就是[服务容器](http://laravelacademy.org/post/93.html)的一个实例。
调用`Cache::extend`可以在默认`App\Providers\AppServiceProvider`中的`boot`方法中完成,或者你也可以创建自己的服务提供者来存放该扩展——只是不要忘了在配置文件`config/app.php`中注册该提供者。
要创建自定义的缓存驱动,首先需要实现`Illuminate\Contracts\Cache\Store`契约,所以,我们的`MongoDB`缓存实现看起来像这样子:
~~~
<?php
namespace App\Extensions;
class MongoStore implements \Illuminate\Contracts\Cache\Store{
public function get($key) {}
public function put($key, $value, $minutes) {}
public function increment($key, $value = 1) {}
public function decrement($key, $value = 1) {}
public function forever($key, $value) {}
public function forget($key) {}
public function flush() {}
public function getPrefix() {}
}
~~~
我们只需要使用`MongoDB`连接实现每一个方法,实现完成后,我们可以完成自定义驱动注册:
~~~
Cache::extend('mongo', function($app) {
return Cache::repository(new MongoStore);
});
~~~
扩展完成后,只需要更新配置文件`config/cache.php`的`driver`选项为你的扩展名称。
如果你在担心将自定义缓存驱动代码放到哪,考虑将其放到Packgist!或者,你可以在`app`目录下创建一个`Extensions`命名空间。然而,记住Laravel并没有一个严格的应用目录结构,你可以基于你的需要自由的组织目录结构。
## 4、缓存标签
> 注意:缓存标签不支持`file`或`database`缓存驱动,此外,在“永久”存储缓存中使用多个标签时,`memcached`之类的驱动有着最佳性能,因为它可以自动清除过期的记录。
### 4.1 存储打上标签的缓存项
缓存标签允许你给相关的缓存项打上同一个标签,然后可以输出被分配同一个标签的所有缓存值。你可以通过传递一个有序的标签名数组来访问被打上标签的缓存。例如,让我们访问一个被打上标签的缓存并将其值放到缓存中:
~~~
Cache::tags(['people', 'artists'])->put('John', $john, $minutes);
Cache::tags(['people', 'authors'])->put('Anne', $anne, $minutes);
~~~
然而,并不只限于使用`put`方法,你可以在处理标签时使用任何混存存储器提供的方法。
### 4.2 访问打上标签的缓存项
要获取被打上标签的缓存项,传递同样的有序标签名数组到`tags`方法:
~~~
$john = Cache::tags(['people', 'artists'])->get('John');
$anne = Cache::tags(['people', 'authors'])->get('Anne');
~~~
通过上面的语句你可以输出所有分配了该标签或标签列表的缓存项,例如,下面这个语句将会移除被打上`people`,`authors`标签的缓存,或者,`Anne` 和 `John`都会从缓存中移除:
~~~
Cache::tags(['people', 'authors'])->flush();
~~~
相比之下,下面这个语句只会移除被打上 `authors`标签的缓存,所以`John`会被移除,而`Anne`不会:
~~~
Cache::tags('authors')->flush();
~~~
## 5、缓存事件
要在每次缓存操作时执行代码,你可以监听缓存触发的事件,通常,你可以将这些缓存处理器代码放到`EventServiceProvider`的`boot`方法中:
~~~
/**
* 注册应用任意其他事件
*
* @param \Illuminate\Contracts\Events\Dispatcher $events
* @return void
*/
public function boot(DispatcherContract $events){
parent::boot($events);
$events->listen('cache.hit', function ($key, $value) {
//
});
$events->listen('cache.missed', function ($key) {
//
});
$events->listen('cache.write', function ($key, $value, $minutes) {
//
});
$events->listen('cache.delete', function ($key) {
//
});
}
~~~