• TOC
    {:toc}

7 数据库

7.1 起步

7.1.1 简介

Laravel 让连接多种数据库以及对数据库进行查询变得非常简单,不论使用原生 SQL、还是查询构建器,还是 Eloquent ORM。目前,Laravel 支持四种类型的数据库系统:

  • MySQL
  • Postgres
  • SQLite
  • SQL Server

7.1.1.1 配置

Laravel 让连接数据库和运行查询都变得非常简单。应用的数据库配置位于config/database.php。在该文件中你可以定义所有的数据库连接,并指定哪个连接是默认连接。该文件中提供了所有支持数据库系统的配置示例。

默认情况下,Laravel 示例环境配置已经为 Laravel Homestead 做好了设置,当然,你也可以按照需要为本地的数据库修改该配置。

  • 读/写连接

有时候你希望使用一个数据库连接做查询,另一个数据库连接做插入、更新和删除,Laravel 使得这件事情轻而易举,不管你用的是原生 SQL,还是查询构建器,还是 Eloquent ORM,合适的连接总是会被使用。

想要知道如何配置读/写连接,让我们看看下面这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
'mysql' => [
'read' => [
'host' => '192.168.1.1',
],
'write' => [
'host' => '196.168.1.2'
],
'driver' => 'mysql',
'database' => 'database',
'username' => 'root',
'password' => '',
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
],

注意我们在配置数组中新增了两个键:read和write,这两个键都对应一个包含单个键“host”的数组,读/写连接的其它数据库配置选项都共用 mysql 的主数组配置。

如果我们想要覆盖主数组中的配置,只需要将相应配置项放到readwrite数组中即可。在本例中,192.168.1.1将被用作“读”连接,而192.168.1.2将被用作“写”连接。两个数据库连接的凭证(用户名/密码)、前缀、字符集以及其它配置将会共享mysql数组中的设置。


7.1.2 运行原生 SQL 查询

配置好数据库连接后,就可以使用DB门面来运行查询。DB门面为每种查询提供了相应方法:select, update, insert, delete, 和statement

7.1.2.1 运行 Select 查询

运行一个最基本的查询,可以使用DB门面select方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php

namespace App\Http\Controllers;

use DB;
use App\Http\Controllers\Controller;

class UserController extends Controller{
/**
* 显示用户列表
*
* @return Response
*/
public function index()
{
$users = DB::select('select * from users where active = ?', [1]);
return view('user.index', ['users' => $users]);
}
}

传递给select方法的第一个参数是原生的SQL语句,第二个参数需要绑定到查询的参数绑定,通常,这些都是where字句约束中的值。参数绑定可以避免SQL注入攻击。

select方法以数组的形式返回结果,数组中的每一个结果都是一个PHP StdClass对象,从而允许你像下面这样访问结果值:

1
2
3
foreach ($users as $user) {
echo $user->name;
}

7.1.2.2 使用命名绑定

除了使用?占位符来代表参数绑定外,还可以使用命名绑定来执行查询:

1
$results = DB::select('select * from users where id = :id', ['id' => 1]);

7.1.2.3 运行插入语句

使用DB门面insert方法执行插入语句。和select一样,改方法将原生SQL语句作为第一个参数,将绑定作为第二个参数:

1
$results = DB::insert('insert into users (id, name) values (?, ?)', [1, 'Dayle']);

7.1.2.4 运行更新语句

update方法用于更新数据库中已存在的记录,该方法返回受更新语句影响的行数:

1
$affected = DB::update('update users set votes = 100 where name = ?', ['John']);

7.1.2.5 运行删除语句

delete方法用于删除数据库中已存在的记录,和update一样,该语句返回被删除的行数:

1
$deleted = DB::delete('delete from users');

7.1.2.6 运行一个通用语句

有些数据库语句不返回任何值,对于这种类型的操作,可以使用DB门面statement方法:

1
DB::statement('drop table users');

7.1.2.7 监听查询事件

如果你想要获取应用中每次 SQL 语句的执行,可以使用listen方法,该方法对查询日志和调试非常有用,你可以在服务提供者中注册查询监听器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php

namespace App\Providers;

use DB;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider{
/**
* 启动所有应用服务
*
* @return void
*/
public function boot()
{
DB::listen(function($query) {
// $query->sql
// $query->bindings
// $query->time
});
}

/**
* 注册服务提供者
*
* @return void
*/
public function register()
{
//
}
}

7.1.3 数据库事务

想要在一个数据库事务中运行一连串操作,可以使用DB门面transaction方法,如果事务闭包中抛出异常,事务将会自动回滚。

如果闭包执行成功,事务将会自动提交。使用transaction方法时不需要担心手动回滚或提交:

1
2
3
4
DB::transaction(function () {
DB::table('users')->update(['votes' => 1]);
DB::table('posts')->delete();
});

7.1.3.1 手动使用事务

如果你想要手动开始事务从而对回滚和提交有一个完整的控制,可以使用DB门面beginTransaction方法:

DB::beginTransaction();

你可以通过rollBack方法回滚事务:

DB::rollBack();

最后,可以通过commit方法提交事务:

DB::commit();

注意:使用DB门面的事务方法还可以用于控制查询构建器和 Eloquent ORM 的事务。


7.1.4 使用多个数据库连接

使用多个数据库连接的时候,可以使用DB门面connection方法访问每个连接。传递给connection方法的连接名对应配置文件config/database.php中相应的连接:

$users = DB::connection('foo')->select(...);

你还可以通过连接实例上的getPdo方法底层原生的 PDO 实例:

$pdo = DB::connection()->getPdo();

  • TOC
    {:toc}

6.6 合约

6.6.1 简介

Laravel的合约是一组由框架(framework)提供的核心服务接口。

例如:

Illuminate\Contracts\Queue\Queue合约规定,列队工作需要的方法。

Illuminate\Contracts\Mail\Mailer合约规定,发送电子邮件的方法。

每个合约都有框架提供的,相应的实现方法。

例如:

Laravel提供一个列队实现方法与各种驱动,和一个由SwiftMailer提供的邮件接收服务。

所有的Laravel合约都在各自的github仓库中更新。这为所有的合约,提供了一个快速参考点,以及一个单一的分离包,用于包开发。

Github参考点

6.6.1.1 合约Vs.门面

Laravel的门面提供一种简单的方法,利用Laravel服务无须输入和提示。这好于使用需要服务容器的合约方法。

但是,使用合约运行你定义依赖类。多余大多数应用,使用门面就好了。

但是,如果你真有的需要额外的松耦合,合约就可以提供,阅读下面的了解更多。

6.6.2 为什么用合约?

关于合约你可能有几个问题要问。为什么全部使用接口?不使用接口会更复杂?

下方是使用接口的重要原因:松耦合简洁性

6.6.2.1 松耦合

首先,让我们来看一个使用紧耦合缓存实现方法的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php

namespace App\Orders;

class Repository
{
/**
* The cache instance.
*/
protected $cache;

/**
* Create a new repository instance.
*
* @param \SomePackage\Cache\Memcached $cache
* @return void
*/
public function __construct(\SomePackage\Cache\Memcached $cache)
{
$this->cache = $cache;
}

/**
* Retrieve an Order by ID.
*
* @param int $id
* @return Order
*/
public function find($id)
{
if ($this->cache->has($id)) {
//
}
}
}

这这个类中,代码是使用紧耦合,到给定的缓存实现方法。

因为是从一个包中取得缓存,所以这是一个紧耦合。

如果这个包的API发生了改变,我们的代码也要随之改变。

同样的,如果我们要使用其他技术(例如:redis),取代正在使用的技术(Memcached),我们还是要修改代码。

我们的类不应该用这些设定,例如,谁为他们提供数据,它是如何提供的等等。

使用下方松耦合方法,不需要绑定注入,进而优化代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

namespace App\Orders;

use Illuminate\Contracts\Cache\Repository as Cache;

class Repository
{
/**
* The cache instance.
*/
protected $cache;

/**
* Create a new repository instance.
*
* @param Cache $cache
* @return void
*/
public function __construct(Cache $cache)
{
$this->cache = $cache;
}
}

现在代码没有连接任何的服务提供者,甚至Laravel。由于合约包中没有实现方法也没有依赖关系,你可以很容易的替换合约,更换缓存技术,而无需修改代码。

6.6.2.2 简洁性

当所有的Laravel的服务,在简单接口中整洁的定义,就可以很容易的给定服务提供的功能。

合约服务为框架功能提了简洁的文档。

当你使用简单接口时,你的代码更容易理解和维护。

6.6.3 合约参考

合约 参考的门面
Illuminate\Contracts\Auth\Factory Auth
Illuminate\Contracts\Auth\PasswordBroker Password
Illuminate\Contracts\Bus\Dispatcher Bus
Illuminate\Contracts\Broadcasting\Broadcaster
Illuminate\Contracts\Cache\Repository Cache
Illuminate\Contracts\Cache\Factory Cache::driver()
Illuminate\Contracts\Config\Repository Config
Illuminate\Contracts\Container\Container App
Illuminate\Contracts\Cookie\Factory Cookie
Illuminate\Contracts\Cookie\QueueingFactory Cookie::queue()
Illuminate\Contracts\Encryption\Encrypter Crypt
Illuminate\Contracts\Events\Dispatcher Event
Illuminate\Contracts\Filesystem\Cloud
Illuminate\Contracts\Filesystem\Factory File
Illuminate\Contracts\Filesystem\Filesystem File
Illuminate\Contracts\Foundation\Application App
Illuminate\Contracts\Hashing\Hasher Hash
Illuminate\Contracts\Logging\Log Log
Illuminate\Contracts\Mail\MailQueue Mail::queue()
Illuminate\Contracts\Mail\Mailer Mail
Illuminate\Contracts\Queue\Factory Queue::driver()
Illuminate\Contracts\Queue\Queue Queue
Illuminate\Contracts\Redis\Database Redis
Illuminate\Contracts\Routing\Registrar Route
Illuminate\Contracts\Routing\ResponseFactory Response
Illuminate\Contracts\Routing\UrlGenerator URL
Illuminate\Contracts\Support\Arrayable
Illuminate\Contracts\Support\Jsonable
Illuminate\Contracts\Support\Renderable
Illuminate\Contracts\Validation\Factory Validator::make()
Illuminate\Contracts\Validation\Validator
Illuminate\Contracts\View\Factory View::make()
Illuminate\Contracts\View\View

6.6.4 如何使用合约

那么,如果使用一个合约的实现方法?这其实很简单。

许多类型的Laravel类,是通过服务容器解决的,包括控制器,事件监听,中间件,列队甚至是路由。

要使用合约,只需要进行类型提示,注册类的接口函数。

例如:下面的事件监听器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?php

namespace App\Listeners;

use App\User;
use App\Events\NewUserRegistered;
use Illuminate\Contracts\Redis\Database;

class CacheUserInformation
{
/**
* The Redis database implementation.
*/
protected $redis;

/**
* Create a new event handler instance.
*
* @param Database $redis
* @return void
*/
public function __construct(Database $redis)
{
$this->redis = $redis;
}

/**
* Handle the event.
*
* @param NewUserRegistered $event
* @return void
*/
public function handle(NewUserRegistered $event)
{
//
}
}

当事件监听器运行完成,服务容器将读取构造函数的类型提示,并注入相应的值。

  • TOC
    {:toc}

6.5 门面

6.5.1 简介

Facades为应用在服务容器中的绑定类,提供一个静态接口。

Laravel附带许多facades,你可以在不知道的情况下正在使用它们。

Laravel的门面作为服务容器中底层类的静态代理,相比于传统静态方法,在维护时能够提供更加易于测试,简明的语法。


6.5.2 使用门面

在Laravel应用的上下文中,门面是一个提供访问容器中对象的类。该机制原理由Facade类实现,Laravel自带的门面,以及创建的自定义门面,都会继承自Illuminate\Support\Facades\Facade基类。

门面类只需要实现一个方法:getFacadeAccessorgetFacadeAccessor 方法定义了从容器中解析什么,然后 Facade 基类使用魔术方法 __callStatic() 从你的门面中调用解析对象。

下面的例子中,将会调用Laravel的缓存系统,浏览代码后,也许你会觉得我们调用了Cache的静态方法get:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php

namespace App\Http\Controllers;

use Cache;
use App\Http\Controllers\Controller;

class UserController extends Controller{
/**
* 为指定用户显示属性
*
* @param int $id
* @return Response
*/
public function showProfile($id)
{
$user = Cache::get('user:'.$id);

return view('profile', ['user' => $user]);
}
}

注意我们在顶部位置引入Cache门面。该门面作为代理访问底层Illuminate\Contracts\Cache\Factory接口的实现方法。我们对门面的所有调用都会被传递给Laravel缓存服务的底层实例。

如果我们查看Illuminate\Support\Facades\Cache类的源码,将会发现其中并没有静态方法get:

1
2
3
4
5
6
7
8
9
10
class Cache extends Facade{
/**
* 获取组件注册名称
*
* @return string
*/
protected static function getFacadeAccessor() {
return 'cache';
}
}

Cache门面继承Facade基类并定义了getFacadeAccessor方法,该方法的工作就是返回服务容器绑定类的别名,当用户引用Cache类的任何静态方法时,Laravel从服务容器中解析Cache绑定,然后在解析出的对象上调用所有请求方法(本例中是get)。


6.5.3 门面类列表

下面列出了每个门面及其对应的底层类,这对深入给定门面的API文档而言是个很有用的工具。服务容器绑定键也被包含进来:

门面 服务器绑定别名
App Illuminate\Foundation\Application app
Artisan Illuminate\Console\Application artisan
Auth Illuminate\Auth\AuthManager auth
Blade Illuminate\View\Compilers\BladeCompiler blade.compiler
Bus Illuminate\Contracts\Bus\Dispatcher
Cache Illuminate\Cache\Repository cache
Config Illuminate\Config\Repository config
Cookie Illuminate\Cookie\CookieJar cookie
Crypt Illuminate\Encryption\Encrypter encrypter
DB Illuminate\Database\DatabaseManager db
DB (Instance) Illuminate\Database\Connection
Event Illuminate\Events\Dispatcher events
File Illuminate\Filesystem\Filesystem files
Gate Illuminate\Contracts\Auth\Access\Gate
Hash Illuminate\Contracts\Hashing\Hasher hash
Lang Illuminate\Translation\Translator translator
Log Illuminate\Log\Writer log
Mail Illuminate\Mail\Mailer mailer
Password Illuminate\Auth\Passwords\PasswordBroker auth.password
Queue Illuminate\Queue\QueueManager queue
Queue (Instance) Illuminate\Contracts\Queue\Queue queue
Queue (Base Class) Illuminate\Queue\Queue
Redirect Illuminate\Routing\Redirector redirect
Redis Illuminate\Redis\Database redis
Request Illuminate\Http\Request request
Response Illuminate\Contracts\Routing\ResponseFactory
Route Illuminate\Routing\Router router
Schema Illuminate\Database\Schema\Blueprint
Session Illuminate\Session\SessionManager session
Session (Instance) Illuminate\Session\Store
Storage Illuminate\Contracts\Filesystem\Factory filesystem
URL Illuminate\Routing\UrlGenerator url
Validator Illuminate\Validation\Factory validator
Validator (Instance) Illuminate\Validation\Validator
View Illuminate\View\Factory view
View (Instance) Illuminate\View\View

  • TOC
    {:toc}

6.4 服务容器

6.4.1 简介

Laravel 服务容器是一个用于管理类依赖和执行依赖注入的强大工具。依赖注入听上去很花哨,其实质是通过构造函数或者某些情况下通过 setter 方法将类依赖注入到类中。

让我们看一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php

namespace App\Jobs;

use App\User;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Contracts\Bus\SelfHandling;

class PurchasePodcast implements SelfHandling
{
/**
* mailer 的實作。
*/
protected $mailer;

/**
* 建立一個新實例。
*
* @param Mailer $mailer
* @return void
*/
public function __construct(Mailer $mailer)
{
$this->mailer = $mailer;
}

/**
* 購買 podcast。
*
* @return void
*/
public function handle()
{
//
}
}

在这个例子中,购买podcast时,PurchasePodcast任务会需要寄送e-mails。因此,将注入能寄送e-mails的服务。由于服务被注入,我们能容易地切换成其它实例。当测试应用程序时,我们能很容易的测试,或者建立假的实例。

深入了解laravel服务容器对构建大型laravel应用很重要,对于贡献代码到laravel核心也很有帮助(很少需要这样做。)


6.4.2 绑定

几乎所有的服务容器绑定都是在服务提供者中注册。所以下方所有的例子将示范在该情况中使用容器。不过,如果类别没有任何依赖,那么就没有将类绑定到容器的必要。并不是需要告诉容器如果构建这些物件,因为它会透PHP的reflection自动解析对象

在一个服务提供者中,可以通过 $this->app 变量访问容器,然后使用 bind方法注册一个绑定,该方法需要两个参数,第一个参数是我们想要注册的类名或接口名称,第二个参数是返回类的实例的闭包:

1
2
3
$this->app->bind('HelpSpot\API', function ($app) {
return new HelpSpot\API($app['HttpClient']);
});

注意,我们接受容器本身作为解析器的一个参数,然后我们可以使用该容器,来解析我们正在构建的对象的子依赖。

  • 绑定一个单例

    singleton 方法绑定,一个只需要解析一次的类或接口到容器,然后接下来对容器的调用将会返回同一个实例:

    1
    2
    3
    $this->app->singleton('FooBar', function ($app) {
    return new FooBar($app['SomethingElse']);
    });
  • 绑定实例

    你还可以使用 instance 方法绑定一个已存在的对象实例到容器,随后对容器的调用将总是返回给定的实例:

    1
    2
    3
    $fooBar = new FooBar(new SomethingElse);

    $this->app->instance('FooBar', $fooBar);

6.4.2.1 绑定接口到实现方法

服务容器的一个非常强大的特性是,其绑定接口到实现方法的能力。

我们假设有一个 EventPusher 接口及其 RedisEventPusher 实现方法,编写完该接口的 RedisEventPusher 实现方法后,就可以将其注册到服务容器:

1
$this->app->bind('App\Contracts\EventPusher', 'App\Services\RedisEventPusher');

这段代码告诉容器当一个类需要 EventPusher 的实现方法将会注入 RedisEventPusher,现在我们可以在构造器或者任何其它通过服务容器注入依赖的地方进行 EventPusher 接口的类型提示:

1
2
3
4
5
6
7
8
9
10
11
use App\Contracts\EventPusher;

/**
* 创建一个新的类实例
*
* @param EventPusher $pusher
* @return void
*/
public function __construct(EventPusher $pusher){
$this->pusher = $pusher;
}

6.4.2.2 上下文绑定

有时侯我们可能有两个类使用同一个接口,但我们希望在每个类中注入不同实现方法。

例如,当系统接到一个新的订单的时候,我们想要通过PubNub而不是 Pusher 发送一个事件。Laravel 定义了一个简单、平滑的方式来定义这种行为:

1
2
3
$this->app->when('App\Handlers\Commands\CreateOrderHandler')
->needs('App\Contracts\EventPusher')
->give('App\Services\PubNubEventPusher');

你甚至还可以传递一个闭包到 give 方法:

1
2
3
4
5
$this->app->when('App\Handlers\Commands\CreateOrderHandler')
->needs('App\Contracts\EventPusher')
->give(function () {
// Resolve dependency...
});
  • 绑定原始值

    有时候你可能有一个获取若干注入类的类,但还需要一个注入的原始值,比如整型数据,你可以轻松使用上下文绑定来注入指定类所需要的任何值:

    1
    2
    3
    $this->app->when('App\Handlers\Commands\CreateOrderHandler')
    ->needs('$maxOrderCount')
    ->give(10);

6.4.2.2 标签

有些情况下,需要解析特定分类下的所有绑定。

例如:你正在构建一个报表规整器,要通过接收有多个不同Report接口数组实现,在注册完 Report 实现方法之后,可以通过tag方法给它们分配一个标签:

SpeedReportMemoryReport打上名为reports的标签。

1
2
3
4
5
6
7
8
9
$this->app->bind('SpeedReport', function () {
//
});

$this->app->bind('MemoryReport', function () {
//
});

$this->app->tag(['SpeedReport', 'MemoryReport'], 'reports');

这些服务被打上标签后,可以通过 tagged 方法来轻松解析它们:

1
2
3
	$this->app->bind('ReportAggregator', function ($app) {
return new ReportAggregator($app->tagged('reports'));
});

6.4.3 解析

有很多方式可以从容器中解析对象,首先,你可以使用 make 方法,该方法接收你想要解析的类名或接口名作为参数:

$fooBar = $this->app->make('FooBar');

其次,你可以以数组方式访问容器,因为其实现了 PHP 的 ArrayAccess 接口:

$fooBar = $this->app['FooBar'];

最后,也是最常用的,你可以简单的通过,在类的构造函数中,对依赖进行类型提示,来从容器中解析对象.包括控制器、事件监听器、队列任务、中间件等都是通过这种方式。在实践中,这是大多数对象从容器中解析的方式。

容器会自动为其解析类注入依赖.

例如:你可以在控制器的构造函数中为应用定义的仓库进行类型提示,该仓库会自动解析并注入该类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php

namespace App\Http\Controllers;

use Illuminate\Routing\Controller;
use App\Users\Repository as UserRepository;

class UserController extends Controller{
/**
* 用户仓库实例
*/
protected $users;

/**
* 创建一个控制器实例
*
* @param UserRepository $users
* @return void
*/
public function __construct(UserRepository $users)
{
$this->users = $users;
}

/**
* 通过指定ID显示用户
*
* @param int $id
* @return Response
*/
public function show($id)
{
//
}
}

6.4.4 容器事件

服务容器在每一次解析对象时都会触发一个事件,可以使用 resolving 方法监听该事件:

1
2
3
4
5
6
7
$this->app->resolving(function ($object, $app) {
// 容器解析所有类型对象时调用
});

$this->app->resolving(function (FooBar $fooBar, $app) {
// 容器解析“FooBar”对象时调用
});

正如你所看到的,被解析的对象将会传递给回调,从而允许你在对象被传递给消费者之前为其设置额外属性。


  • TOC
    {:toc}

6.2 应用目录结构

6.2.1 简介

Laravel 应用默认的目录结构试图为不管是大型应用还是小型应用提供一个好的起点,当然,你可以自己按照喜好重新组织应用目录结构,Laravel 对类在何处被加载没有任何限制——只要 Composer 可以自动载入它们即可。


6.2.2 根目录

新安装的 Laravel 应用包含许多文件夹:

目录名称 目录说
app 目录包含了应用的核心代码
bootstrap 目录包含了少许文件用于框架的启动和自动载入配置
bootstrap/cache 用于包含框架生成的启动文件以提高性能
config 目录包含了应用所有的配置文件
database 目录包含了数据迁移及填充文件,如果你喜欢的话还可以将其作为 SQLite 数据库存放目录
public 目录包含了前端控制器和资源文件(图片、JavaScript、CSS等)
resources 目录包含了视图文件及原生资源文件(LESS、SASS、CoffeeScript),以及本地化文件
storage 目录包含了编译过的Blade模板、基于文件的session、文件缓存,以及其它由框架生成的文件
storage/app 目录用于存放应用要使用的文件
storage/framework 目录用于存放框架生成的文件和缓存
storage/logs 目录包含应用的日志文件
tests 目录包含自动化测试,其中已经提供了一个开箱即用的PHPUnit示例
vendor 目录包含Composer依赖

6.2.3 App目录

默认情况下,在项目的App文件夹,并且被 Composer 通过 PSR-4自动载入标准 自动加载。可以通过Artisan命令app:name来修改该命名空间。

App目录下有多个子目录。

ConsoleHttp目录提供了进入应用核心的API。

它们是两个向应用发布命令的方式。

Console目录包含了所有的Artisan命令。

Http目录包含了控制器,中间件请求等等。

Jobs目录是放置列队任务的地方,应用中的任务可以被队列化,也可以在当前请求生命周期内同步执行。

Events目录是放置事件类的地方,事件可以同于通知应用其他部分给定的动作已经发生,并提供灵活的解耦的处理。

Listeners目录包含事件的处理器类,处理器接收一个事件并提供对该事件发生后的响应逻辑。

例如,UserRegstered事件可以被SendWelcomeEmail监听器处理。

Exceptions目录包含应用的异常处理器,同时还是处理应用抛出的异常的好地方。

注意:app目录中的很多类都可以通过Artisan命令生成,要查看所有命令,使用命令:php artisan list make


  • TOC
    {:toc}

6.3 服务提交者

6.3.1 简介

服务提供者是所有laravel应用启动的中心,你自己的应用以及所有Laravel的核心服务都是通过服务提供者启动。

但是,我们所谓的启动指的是什么?通常,这意味着注册事物,包括注册服务容器绑定,事件监听器,中间件甚至路由。服务提供者是应用配置的中心。

如果你打开Laravel自带的config/app.php文件,将会看到一个providers数组,这里就是应用所要加载的所有服务提供者类,当然,其中很多是延迟加载的,也就是说不是每次请求都会被加载,只有真的用到它们的时候才会加载。

本章里你将会学习如何编写自己的服务提供者并在Laravel应用中注册它们。


6.3.2 编写服务提供者

所以得服务提供者继承自Illuminate\Support\ServiceProvider类。继承该抽象类要求至少在服务提供者中定义一个方法:register。在register方法内,你唯一要做的事情就是绑事物到服务容器,不要尝试在其中注册任何时间监听器,路由或者任何其它功能。

通过Artisan命令make:provider可以简单生成一个新的提供者:

php artisan make:provider RiakServiceProvider

文件被创建到App/Providers/RiakServiceProvider.php

6.3.2.1 register方法

正如前面所提到的,在register方法中只绑定事物到服务容器,而不要做其它事情,否则,一不小心就会用到一个尚未被加载的服务提供者提供的服务。

现在来看看一个基本的服务提供者是什么样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php

namespace App\Providers;

use Riak\Connection;
use Illuminate\Support\ServiceProvider;

class RiakServiceProvider extends ServiceProvider{
/**
* 在容器中注册绑定.
*
* @return void
*/
public function register()
{
$this->app->singleton('Riak\Contracts\Connection', function ($app) {
return new Connection(config('riak'));
});
}
}

编辑的文件为App/Providers/RiakServiceProvider.php

这个服务提供者只定义了一个register方法,并在服务容器中使用此方法定义了一份Riak/connection的实例。如果不清楚它是如何运作的,查看下一章节服务容器

6.3.2.2 boot方法

如果我们想要在服务提供者中注册视图composer该怎么做?这就要用到boot方法,该方法在所有服务提供者被注册以后才会被调用,这就是说我们可以在其中访问框架已注册的所有其他服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class EventServiceProvider extends ServiceProvider{
/**
* Perform post-registration booting of services.
*
* @return void
*/
public function boot()
{
view()->composer('view', function () {
//
});
}

/**
* 在容器中注册绑定.
*
* @return void
*/
public function register()
{
//
}
}
  • boot方法的依赖注入

我们可以在boot方法中类型提升依赖,服务容器会自动注册你需要的依赖:

1
2
3
4
5
6
7
use Illuminate\Contracts\Routing\ResponseFactory;

public function boot(ResponseFactory $factory){
$factory->macro('caps', function ($value) {
//
});
}

6.3.3 注册服务提供者

所有服务提供者都是通过配置文件config/app.php中进行注册,该文件包含了一个列出所有服务提供者名字的providers数组,默认情况下,其中列出了所有核心服务提供者,这些服务提供者启动Laravel核心组件,比如邮件、列队、缓存等等。

要注册你自己的服务提供者,只需要将其追加到该数组中:

1
2
3
4
'providers' => [
// 其它服务提供者
App\Providers\AppServiceProvider::class,
],

6.3.4 延迟加载服务提供者

如果你的提供者仅仅只是在服务容器中注册绑定,你可以选择在延迟加载该绑,定直到注册绑定真的需要时再加载,延迟加载这样的一个提供者将会提升应用的性能,因为它不会在每次请求时都从文件系统加载。

想要延迟加载一个提供者,设置defer属性为true并定义一个provides方法,该方法返回该提供者注册的服务容器绑定:

想要延迟加载一个提供者,设置defer属性为true并定义一个provides方法,该方法返回该提供者注册的服务容器绑定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?php

namespace App\Providers;

use Riak\Connection;
use Illuminate\Support\ServiceProvider;

class RiakServiceProvider extends ServiceProvider{
/**
* 服务提供者加是否延迟加载.
*
* @var bool
*/
protected $defer = true;

/**
* 注册服务提供者
*
* @return void
*/
public function register()
{
$this->app->singleton('Riak\Contracts\Connection', function ($app) {
return new Connection($app['config']['riak']);
});
}

/**
* 获取由提供者提供的服务.
*
* @return array
*/
public function provides()
{
return ['Riak\Contracts\Connection'];
}

}

Laravel编译并保存所有延迟服务者提供的服务以及服务提供者的类名。

然后,只有当你尝试解析其中某个服务时Laravel才会加载其服务提交者。


  • TOC
    {:toc}

6 laravel框架

6.1 一次请求的生命周期

6.1.1 简介

当我们使用现实世界中的任何工具时,如果理解了该工具的工作原理,那么用起来就会得心应手,应用开发也是如此。当你理解了开发工具如何工作,用起来就会更加游刃有余。

本文档的目标就是从一个更好、更高层面向你阐述 Laravel 框架的工作原理。通过对框架更全面的了解,一切都不再那么神秘,你将会更加自信的构建应用。

如果你不能马上理解所有这些条款,不要失去信心!先试着掌握一些基本的东西,你的知识将会随着对本文档的探索而不断提高。


6.1.2 生命周期预览

6.1.2.1 第一件事

Laravel应用的所有请求入口都是public/index.php文件,所有请求都会被web服务器(Apache/Nginx)导向这个文件。index.php文件包含的代码并不多,但是,是加载框架其他部分的起点。

index.php文件载入Composer生成的自动加载设置,然后从bootstrap/app.php脚本获取Laravel应用实例,Laravel的第一个动作就是创建服务容器实例。

6.1.2.2 HTTP/Console内核

接下来,请求被发送到 HTTP 内核或 Console 内核,这取决于进入应用的请求类型。这两个内核是所有请求都要经过的中央处理器,现在,就让我们聚焦在位于 app/Http/Kernel.php 的 HTTP 内核。

HTTP 内核继承自 Illuminate\Foundation\Http\Kernel 类,该类定义了一个 bootstrappers 数组,这个数组中的类在请求被执行前运行,这些 bootstrappers 配置了错误处理、日志、检测应用环境以及其它在请求被处理前需要执行的任务。

HTTP 内核还定义了一系列所有请求在处理前需要经过的 HTTP 中间件,这些中间件处理 HTTP 会话的读写、判断应用是否处于维护模式、验证 CSRF 令牌等等。

HTTP 内核的标志性方法 handle 处理的逻辑相当简单:获取一个 Request,返回一个 Response,把该内核想象作一个代表整个应用的大黑盒子,输入 HTTP 请求,返回 HTTP 响应。

  • 服务提供者

    内核启动过程中最重要的动作之一就是为应用载入服务提供者,应用的所有服务提供者都被配置在 config/app.php 配置文件的 providers 数组中。首先,所有提供者的 register 方法被调用,然后,所有提供者被注册之后,boot 方法被调用。

    服务提供者负责启动框架的所有各种各样的组件,比如数据库、队列、验证器,以及路由组件等,正是因为他们启动并配置了框架提供的所有特性,服务提供者是整个 Laravel 启动过程中最重要的部分。

  • 分发请求

    一旦应用被启动并且所有的服务提供者被注册,Request 将会被交给路由器进行分发,路由器将会分发请求到路由或控制器,同时运行所有路由指定的中间件。


6.1.3 聚集服务提交者

服务提供者是启动 Laravel 应用中最关键的部分,应用实例被创建后,服务提供者被注册,请求被交给启动后的应用进行处理,整个过程就是这么简单!

对 Laravel 应用如何通过服务提供者构建和启动有一个牢固的掌握非常有价值,当然,应用默认的服务提供者存放在 app/Providers 目录下。

默认情况下,AppServiceProvider 是空的,这里是添加自定义启动和服务容器绑定的最佳位置,当然,对大型应用,你可能希望创建多个服务提供者,每一个都有着更加细粒度的启动。

阅读感言

上面说了那么多,实际上可以理解为下方的简单流程。

  1. public/index.phplaravel访问的起点。创建服务容器。

  2. 请求进入内核(HTTP或Console)。

  3. HTTP内核启动,laravel最重要的应用——服务提供者。

  4. 服务提供者启动各种各样的组件。

  5. 当所有的服务提供者被注册,路由开始工作。

默认服务提供者存放在app/Providers


  • TOC
    {:toc}

5.7 Blade 模板

5.7.1 简介

Blade 是 Laravel 提供的一个非常简单但很强大的模板引擎,不同于其他流行的PHP模板引擎,Blade在视图中并不约束你使用PHP原生代码。所有的Blade视图都会被编译成原生PHP代码并缓存起来直到被修改,这意味着对应用的性能而言Blade基本上是零开销。Blade视图文件使用.blade.php文件扩展并存放在resources/views目录下。


5.7.2 模板继承

5.7.2.1 定义页面布局

使用Blade的两个最大的优点是模板继承和切片,开始之前先看一个例子。

首先,检测一个主页面布局,由于大多数web应用在不同页面中使用同一个布局,可以很方便的将这个布局定义为一个单独的Blade页面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- 存放在 resources/views/layouts/master.blade.php -->

<html>
<head>
<title>App Name - @yield('title')</title>
</head>
<body>
@section('sidebar')
This is the master sidebar.
@show

<div class="container">
@yield('content')
</div>
</body>
</html>

正如所看到的,该文件包含典型的HTML标记,然而,注意@section@yield指令,前者正如其名字所暗示的,定义了一个内容的片段,而后者用于显示给定片段的内容。

现在我们已经为应用定义了一个布局,接下来让我们定义继承该布局的子页面。

5.7.2.2 扩展页面布局

定义子页面的时候,可以使用Blade的@extends指令来指定子页面所继承的布局,继承一个Blade布局的视图将会使用@section指令注入内容到布局的片段中,记住,如上面例子所示,这些片段的内容将会显示在布局中使用@yield的地方:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- 存放在 resources/views/child.blade.php -->

@extends('layouts.master')

@section('title', 'Page Title')

@section('sidebar')
@parent

<p>This is appended to the master sidebar.</p>
@endsection

@section('content')
<p>This is my body content.</p>
@endsection

在本例中,sidebar片段使用@parent指令来追加内容到布局中sidebar,@parent指令在视图渲染时将会被布局中的内容替换。

当然,和原始PHP视图一样,Blade视图可以通过view方法直接从路由中返回:

1
2
3
Route::get('blade', function () {
return view('child');
});

5.7.3 数据显示

可以通过两个花括号包裹变量来显示传递到视图的数据,比如,如果给出如下路由:

1
2
3
Route::get('greeting', function () {
return view('welcome', ['name' => 'Samantha']);
});

那么可以通过如下方式显示name变量的内容:

Hello, {{ $name }}.

当然,不限制显示到视图中的变量内容,你还可以输出任何PHP函数,实际上,可以将任何PHP代码放到Blade模板语句中:

The current UNIX timestamp is {{ time() }}.

注意:Blade的{{}}语句已经经过PHP的htmlentities函数处理以避免XSS攻击。

5.7.3.1 Blade & JavaScript 框架

由于很多Javascript框架也是用花括号表示,要显示在浏览器中的表达式,可以使用@告诉Blade渲染引擎该表达式应该保持原生格式不作改动。例如:

1
2
3
<h1>Laravel</h1>

Hello, @{{ name }}.

这里@符合会被Blade移除。而且,Blade引擎会保留{{name}}表单式,如此一来可让其它Javascript框架使用。

5.7.3.2 输出存在的数据

有时候想要输出一个变量,但是不确定该变量是否被设置,我们可以通过如下PHP代码:

{{ isset($name) ? $name : 'Default' }}

处理上方的方法,Blade还提供了更简单的方式:

{{ $name or 'Default' }}

在这里,如果$name变量存在,将会显示,否则将显示Default

5.7.3.3 显示原生数据

默认情况下,Blade的{{ }}语法已经通过PHP的htmlentities函数处理以避免XSS攻击,如果不想要数据被处理,可以使用如下语法:

Hello, {!! $name !!}.

注意:对于用户输入的内容要当心,要使用双花括号以避免直接输出HTML代码。


5.7.4 流程控制

除了模板继承和数据显示之外,Blade还为常用的PHP流程提供了便利操作,比如条件语句和循环,这些快捷操作提供了一个干净,简单的方式来处理PHP的流程控制,同时保持和PHP相应语句相似。

5.7.4.1 If 语句

可以使用@if,@elseif,@else,@endif,来构造if语句,这些指令函数和PHP的相同:

1
2
3
4
5
6
7
@if (count($records) === 1)
I have one record!
@elseif (count($records) > 1)
I have multiple records!
@else
I don't have any records!
@endif

为了方便,Blade还提供了@unless指令:

1
2
3
@unless (Auth::check())
You are not signed in.
@endunless

5.7.4.2 循环

除了条件语句,Blade还提供了简单指令处理PHP支持的循环结构,同样,这些指令函数和PHP一样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@for ($i = 0; $i < 10; $i++)
The current value is {{ $i }}
@endfor

@foreach ($users as $user)
<p>This is user {{ $user->id }}</p>
@endforeach

@forelse ($users as $user)
<li>{{ $user->name }}</li>
@empty
<p>No users</p>
@endforelse

@while (true)
<p>I'm looping forever.</p>
@endwhile

5.7.4.3 包含子视图

Blade的@include指令允许你很简单的在一个视图中包含另一个Blade视图,所有父级视图中变量在被包含的子视图中依然有效:

1
2
3
4
5
6
7
<div>
@include('shared.errors')

<form>
<!-- Form Contents -->
</form>
</div>

尽管被包含的视图继承所有父视图中的数据,还可以传递额外参数到被包含的视图:

@include('view.name', ['some' => 'data'])

注意:不要再Blade视图中使用__DIR____FIFL__常量,因为他们会指向缓存视图的路径。

5.7.4.4 为集合渲染视图

使用Blade的@each指令将循环或引入 压缩成一行:

@each('view.name', $jobs, 'job')

第一个参数为:对数组或集合的每个元素渲染的局部视图。

第二个参数为:要迭代的数组或集合。

第三个参数为:迭代时被分配到视图中的变数名称。

举例来说:如果迭代一个jobs数组,通常希望在局部视图中透过job变数存取每一个job。

传递第四个参数至@each指令。此参数为:当给定的数组为空时,将会被渲染的视图。

@each('view.name', $jobs, 'job', 'view.empty')

5.7.4.5 注释

Blade还允许在视图中定义注释,然而,不同于HTML注释,Blade注释并不会包含到HTML中被返回:

{{-- This comment will not be present in the rendered HTML --}}

5.7.5 服务注入

@inject指令可以用于从服务容器中获取服务,传递给@inject的第一个参数是服务将要被放置的变量名,第二个参数是要解析的服务类名或接口名:

1
2
3
4
5
@inject('metrics', 'App\Services\MetricsService')

<div>
Monthly Revenue: {{ $metrics->monthlyRevenue() }}.
</div>

5.7.6 扩展 Blade

Blade还允许你自定义指令,可以使用directive方法来注册一个指令。当Blade编辑器遇到该指令,将会传入参数并调用提供的回调。

下面的例子创建了一个@datetime($var)指令,格式化给定的$var:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php

namespace App\Providers;

use Blade;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
/**
* Perform post-registration booting of services.
*
* @return void
*/
public function boot()
{
Blade::directive('datetime', function($expression) {
return "<?php echo with{$expression}->format('m/d/Y H:i'); ?>";
});
}

/**
* 在容器中注册绑定.
*
* @return void
*/
public function register()
{
//
}
}

laravel 辅助函数with被用在该指令中,with方法返回给定的对象或值,允许方法链。最终该指令生成的PHP代码如下:

1
<?php echo with($var)->format('m/d/Y H:i'); ?>

  • TOC
    {:toc}

5.6 视图

5.6.1 基本使用

视图包含的HTML代码并将控制器逻辑和表面逻辑进行分离。视图文件存放在resources/views目录。

下面是一个简单视图:

1
2
3
4
5
6
7
<!-- 该视图存放 resources/views/greeting.php -->

<html>
<body>
<h1>Hello, <?php echo $name; ?></h1>
</body>
</html>

由于这个视图存放在resources/views/greeting.php,可以在全局的辅助函数view中这样返回它:

1
2
3
Route::get('/', function () {
return view('greeting', ['name' => 'James']);
});

view的第一个参是resources/views目录下相应的视图文件的名字,第二个参数是一个数组,该数组包含了在视图中的有效数据。

例子中,传递的是一个name变量,在视图中通过执行echo将其显示出来。

当然,视图还可以嵌套在resources/views的子目录中,用.来引用嵌套视图。

例如,视图存放路径是resources/views/admin/profile.php,那可以这样应用它:

return view('admin.profile', $data);

5.6.1.1 判断视图是否存在

如果需要判断视图是否存在,可调用不带参数的view之后,使用exists方法,如果视图存在则返回ture:

1
2
3
if (view()->exists('emails.customer')) {
//
}

调用不带参数的view时,将会返回一个Illuminate\Contracts\View\Factory实例,从而调用该类上所有方法。


5.6.2 视图数据

5.6.2.1 传递数据到视图

在上述例子中可以看到,可以简单通过数组方式将数据传递到视图:

1
return view('greetings', ['name' => 'Victoria']);

以这种方式传递数据,$data应该是一个键值对应该数组,在视图中,就可以使用响应的键来访问数组值。

例如<?php echo $key; ?>。还可以通过with方法添加独立的数据片段到视图:

1
$view = view('greeting')->with('name', 'Victoria');

5.6.2.2 在视图间共享数据

有时候需要在所有视图之间共享数据片段,这时可以使用视图类的share方法,通常,需要在服务提供者的boot方法中调用share方法,你可以将其添加到AppServiceProvider或生成独立的服务提供者来存放它们:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php

namespace App\Providers;

class AppServiceProvider extends ServiceProvider
{
/**
* 启动所有应用服务
*
* @return void
*/
public function boot()
{
view()->share('key', 'value');
}

/**
* 注册服务提供者
*
* @return void
*/
public function register()
{
//
}
}

5.6.3 视图Composer

视图Composer就是在视图被渲染前,会呼叫的闭包或类别方法。

如果想在每次渲染都绑定一些数据,可以使用视图Composer将这种的程序逻辑放到同一个地方。

首先在服务提供者中注册视图Composer。

之后将使用view访问Illuminate\Contracts\View\Factory 的底层实现。

laravel不会包含默认的视图Composer目录,可以随意设置路径。

例如,创建一个App\Http\ViewComposers目录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class ComposerServiceProvider extends ServiceProvider
{
/**
* 在容器中注册绑定.
*
* @return void
* @author http://laravelacademy.org
*/
public function boot()
{
// 使用基于类的composers...
view()->composer(
'profile', 'App\Http\ViewComposers\ProfileComposer'
);

// 使用基于闭包的composers...
view()->composer('dashboard', function ($view) {
});
}

/**
* 注册服务提供者.
*
* @return void
*/
public function register()
{
//
}
}

如果创建一个新的服务提供者包含视图Composer注册,需要添加该服务提供者到配置文件config/app.phpproviders数组中。

现在我们已经注册了Composer,每次profile视图被渲染时都会执行PrefileComposer@compose,接下来定义Composer类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?php

namespace App\Http\ViewComposers;

use Illuminate\Contracts\View\View;
use Illuminate\Users\Repository as UserRepository;

class ProfileComposer
{
/**
* 用户仓库实现.
*
* @var UserRepository
*/
protected $users;

/**
* 创建一个新的属性composer.
*
* @param UserRepository $users
* @return void
*/
public function __construct(UserRepository $users)
{
// Dependencies automatically resolved by service container...
$this->users = $users;
}

/**
* 绑定数据到视图.
*
* @param View $view
* @return void
*/
public function compose(View $view)
{
$view->with('count', $this->users->count());
}
}

视图被渲染前,Composer类的composer方法被调用,同时Illuminate\Contracts\View\View被注入该方法,从而可以使用with方法来绑定数据到视图。

注意:所有视图Composer都通过服务容器被解析,所以可以在Composer类的构造函数中声明任何需要的依赖。

5.6.3.1 添加 Composer 到多个视图

可以传递视图数组作为composer方法的第一个参数,来一次将视图Composer添加到多个视图:

1
2
3
4
view()->composer(
['profile', 'dashboard'],
'App\Http\ViewComposers\MyViewComposer'
);

composer方法接受*通配符,从而允许将一个Composer添加到所有视图:

1
2
3
view()->composer('*', function ($view) {
//
});

5.6.4 视图创建器

视图创造器和视图Composer非常类似。不同在于,前者在视图实例化之后立即失效而不是等到视图即将渲染。

使用create方法可注册一个视图创建器:

1
view()->creator('profile', 'App\Http\ViewCreators\ProfileCreator');

  • TOC
    {:toc}

5.5 响应

5.5.1 基本响应

所有路由和控制器都会返回某种被发送到用户浏览器的响应,laravel提供多种不同的方式来返回响应,最基本的响应就是从路由或控制器返回一个简单的字符串:

1
2
3
Route::get('/', function () {
return 'Hello World';
});

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

5.5.1.1 Response对象

大多数路由和控制器动作都会返回一个完整的Illuminate\Http\Response实例或视图,返回一个完整的Response实例允许你自定义响应的HTTP状态码和头信息。Responses实例继承自Symfony\Component\HttpFoundation\Response类,该类提供了一系列方法用于创建HTTP响应:

1
2
3
4
5
6
use Illuminate\Http\Response;

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

为了方便,还可以使用辅助函数response:

1
2
3
4
Route::get('home', function () {
return response($content, $status)
->header('Content-Type', $value);
});

注意:要查看完整的Response方法列表,看可以查看API文档Symfony API 文档

5.5.1.2 添加响应头

大部分响应方法可以用方法链形式调用,从而使得可以建立平滑的响应。

例如,你可以在响应送出给使用者之前,使用header方法增加一系列的响应头到响应:

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

或者,可以使用withHeaders方法指定要增加至回应的响应头数组:

1
2
3
4
5
6
return response($content)
->withHeaders([
'Content-Type' => $type,
'X-Header-One' => 'Header Value',
'X-Header-Two' => 'Header Value',
]);

5.5.1.3 添加Cookie到响应

透过响应实例的函数cookie可以轻松添加Cookie到响应:

1
2
return response($content)->header('Content-Type', $type)
->cookie('name', 'value');

cookie方法接收额外的可选参数,使你可以进一步自定cookies的属性:

1
->cookie($name, $value, $minutes, $path, $domain, $secure, $httpOnly)

默认情况下,laravel框架生成的Cookie经过了加密和签名,以免在客户端被纂改。

如果想要让特定的Cookie子集在生成时取消加密,可以使用中间件App\Http\Middleware\EncryptCookies$except属性来排除这些Cookie:

1
2
3
4
5
6
7
8
/**
* 不需要被加密的cookies名称
*
* @var array
*/
protected $except = [
'cookie_name',
];

5.5.2 其他响应类型

辅助函数response可以很方便地用来生成其他类型的响应实例,当无参数调用response时会返回Illuminate\Contracts\Routing\ResponseFactory的一个实现类实例契约,该契约提供了一些有用的方法来生成响应。

5.5.2.1 视图

如果需要控制响应状态和响应头,并还需要返回一个视图作为响应内容,可以使用view方法:

return response()->view('hello', $data)->header('Content-Type', $type);

当然,如果不需要传递自定义的HTTP状态码和头信息,只需要简单使用全局函数view即可。

5.5.2.2 JSON

json方法会自动将Content-Type头设置为application/json,并使用PHP函数json_encode方法将给定数组转化为JSON:

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

如果要创建一个JSONP响应,可以在json方法之后调用setCallback方法:

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

5.5.2.3 文件下载

download方法用于生成强制用户浏览器下载给定路由文件的响应。download文件接受文件名作为第二个参数,该参数决定用户下载文件的显示名称,可以将HTTP头信息作为第三个参数传递到该方法:

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

注意:管理文件下载的 Symfony HttpFoundation 类要求被下载文件有一个ASCII文件名。


5.5.3 重定向

重定向响应是Illuminate\Http\RedirectResponse类的实例,其中包含了必须的头信息将用户重定向到另一个 URL,有很多方式来生成 RedirectResponse 实例,最简单的方法就是使用全局辅助函数 redirect

1
2
3
Route::get('dashboard', function () {
return redirect('home/dashboard');
});

有时候想要将用户重定向到上一个请求的位置。

例如,表单提交后,验证不通过,就可以使用辅助函数back返回到前一个URL(使用该方法之前确保路由使用了web中间件组或都使用了session 中间件):

1
2
3
4
Route::post('user/profile', function () {
// 验证请求...
return back()->withInput();
});

5.5.3.1 重定向到命名路由

如果调用不带参数的redirect方法,会返回一个Illuminate\Routing\Redirector实例,然后可以调用该实例上的任何方法。

例如:为了生成一个RedirectResponse到命令路由,可以使用route方法:

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

如果路由中有参数,可以将其作为第二个参数传递到route方法:

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

如果要重定向到带ID参数的路由(Eloquent 模型绑定),可以传递模型本身,ID会被自动解析出来:

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

5.5.3.2 重定向到控制器动作

还可以生成重定向到控制器动作,只需传递控制器和动作名到action方法即可。记住,不需要指定控制器的完整命名。laravel的RouteServiceProvider会自动设置默认的控制器命名:

return redirect()->action('HomeController@index');

当然,如果控制器路由要求参数,可以将参数作为第二个参数传递给action方法:

return redirect()->action('UserController@profile', [1]);

5.5.3.3 带一次性 Session 数据的重定向

重定向到一个新的URL并将数据存储到一次性Session中通常是同时完成的,为了方便,可以创建一个RedirectResponse实例然后在同一方法上将数据存储到Session,这种方式在action之后存储状态信息特别方便:

1
2
3
4
Route::post('user/profile', function () {
// 更新用户属性...
return redirect('dashboard')->with('status', 'Profile updated!');
});

当然,用户重定向到新页面之后,你可以从Session中取出并显示这些一次性信息。

例如,使用Blade语法实现:

1
2
3
4
5
@if (session('status'))
<div class="alert alert-success">
{{ session('status') }}
</div>
@endif

5.5.4 响应宏

如果想要定义一个自定义的响应并且在多个路由和控制器中复用,可以使用Illuminate\Contracts\Routing\ResponseFactory类,或者Response函数中的macro方法。

例如:在某个服务提供者的boot方法中编写代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php

namespace App\Providers;

use Response;
use Illuminate\Support\ServiceProvider;

class ResponseMacroServiceProvider extends ServiceProvider
{
/**
* Perform post-registration booting of services.
*
* @return void
*/
public function boot()
{
Response::macro('caps', function ($value) {
return Response::make(strtoupper($value));
});
}
}

macro方法接收响应名称作为第一个参数,闭包函数作为第二个参数,macro的闭包在ResponseFactory类或Response函数中的macro方法时执行:

return response()->caps('foo');
0%