• 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');

  • TOC
    {:toc}

5.4 请求

5.4.1 获取请求

通过依赖注入获取当前HTTP请求实例,应该在控制器的构造函数或方法中对Illuminate\Http\Request类进行类型提示,当前请求实例会被服务容器自动注入:

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

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Routing\Controller;

class UserController extends Controller
{
/**
* 存储新用户
*
* @param Request $request
* @return Response
*/
public function store(Request $request)
{
$name=$request->input('name');

//
}
}

如果你的控制器方法还期望获取路由参数输入,只需要将路由参数置于其它依赖之后即可,例如,如果你的路由定义如下:

Route::put('user/{id}','UserController@update');

依然可以对Illuminate\Http\Request进行类型提示并通过如下方式定义控制器方法来访问路由参数:

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 Illuminate\Http\Request;
use Illuminate\Routing\Controller;

classUser Controller extends Controller
{
/**
* 更新指定用户
*
* @param Request $request
* @param int $id
* @return Response
*/
public function update(Request $request,$id)
{
//
}
}

5.4.1.1 基本请求信息

Illuminate\Http\Request实例提交了多个方法来检查应用的HTTP请求,laravel的Illuminate\Http\Request继承自Symfony\Component\HttpFoundation\Request类,下面演示了一些有用的方法:

  • 获取请求URI

    path方法将会返回请求的URI(返回的是域名之后的部分),如果进入的请求路径是http://domain.com/foo/bar,则path方法将会返回foo/bar:

      $uri=$request->path();
    

    is方法允许你验证进入的请求是否与给定模式匹配。使用该方法可以使用*通用符:

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

    想要获取完整的URL,而不仅仅是路径信息,可以使用请求实例中的urlfullUrl方法:

    1
    2
    $url=$request->url();
    $url = $request->fullUrl();
  • 获取请求方法

    method方法将会返回请求的HTTP请求方式。你可以使用isMethod方法来验证HTTP请求方式是否匹配给定字符串:

    1
    2
    3
    4
    $method=$request->method();
    if($request->isMethod('post')){
    //
    }

5.4.1.2 PSR-7 请求

PSR-7标准指定了HTTP信息接口,包括请求和响应。如果想要获取PSR-7请求实例,首先需要安装一些库,laravel使用Symfony HTTP Message Bridge组件,将原的laravel请求和响应转化为PSR-7兼容的请求:

1
2
composer require symfony/psr-http-message-bridge
composer require zendframework/zend-diactoros

安装完这些库之后,只需要在路由或控制器中通过,对请求类型进行类型提示就可以获取RSR-7请求:

1
2
3
4
5
use Psr\Http\Message\ServerRequestInterface;

Route::get('/', function (ServerRequestInterface $request) {
//
});

如果从路由或控制器返回的是PSR-7响应实例,则其将会自动转化为laravel响应实例并显示出来。


5.4.2 获取输入信息

  • 获取特定输入值

    通过Illuminate\Http\Request 实例,使用简单的方法就可以取得所有的使用者输入资料。

    不需要担心发出请求时使用的HTTP动词,获取输入资料的方式都是相同的。

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

    此外,可以使用Illuminate\Http\Request 的动态属性存取使用者输入。

    例如,如果你的应用程序的表单含有一个name字段,可以使用下面的方式提交:

      $name = $request->name;
    

    还可以传统一个默认值作为第二个参数给input方法,如果请求输入值在当前请求未出现时该值将会返回:

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

    处理表单数组输入时,可以使用.来访问数组输入:

      $input = $request->input('products.0.name');
      $names = $request->input('products.*.name');
    
  • 判断输入值是否出现

    判断值是否在请求中出现,可以使用has方法,如果值出现且不为空,has方法返回true:

    1
    2
    3
    if ($request->has('name')) {
    //
    }
  • 获取所有输入数据

    还可以通过all方法获取所有输入数据:

      $input = $request->all();
    
  • 获取部分输入数据

    如何需要取出输入数据的子集,可以使用onlyexcept方法,这两个方法都接收一个数组或动态列表作为唯一参数:

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

5.4.2.1 上一次请求输入

laravel 允许你在两次请求之间保存输入数据,这个特性在检测校验数据失败后需要重新填充表单数据时很有用。

但如果使用的是laravel内置的验证服务,则不需要手动使用这些方法,因为一些laravel内置的校验会自动调用它们。

  • 将输入存储到一次性Session

    Illuminate\Http\Request实例的flash方法会将当前输入存放到一次性Session中,下次使用者发出请求至应用程序时就可以使用它们:

      $request->flash();
    

    还可以使用flashOnlyflashExcept方法将输入数据子集存储到Session中:

      $request->flashOnly('username', 'email');
      $request->flashExcept('password');
    
  • 将输入存储到一次性Session然后重定向

    可能常常需要使用Session中的数据,并重新定向到前一页。可以简单使用withInput方法来将输入数据链接到redirect后面:

      return redirect('form')->withInput();
      return redirect('form')->withInput($request->except('password'));
    
  • 获取上次输入数据

    要从Session中取出上次请求的输入数据,可以使用Request实例的old方法。old方法提供了便利方式从Session中取出数据:

      $username = $request->old('username');
    

    laravel还提供了一个全局的辅助函数old,如果在Blade模板中显示上次输入数据,使用辅助函数old更方便,如果给定参数没有对应输入,返回null:

      {{ old('username') }}
    

5.4.2.2 Cookies

  • 从请求中取出Cookies

    laravel 框架创建的所有cookies都经过加密并使用一个认证码进行签名,这意味着如果客户端修改了它们则需要对其进行有效性验证。使用Illuminate\Http\Request类中的 cookie方法从请求中获取cookie的值:

      $value = $request->cookie('name');
    
  • 新增Cookies到回应

    laravel 提供一个全局辅助函数cookie作为一个简单工厂来生成新的Symfony\Component\HttpFoundation\Cookie实例,新增的cookie通过withCookie方法被附加到Illuminate\Http\Response实例:

      $response = new Illuminate\Http\Response('Hello World');
      $response->withCookie(cookie('name', 'value', $minutes));
      return $response;
    

    想要创建一个长期有效的cookie,可以使用cookie工厂的forever方法:

      $response->withCookie(cookie()->forever('name', 'value'));
    

5.4.2.3 文件上传

  • 获取上传的文件

    使用Illuminate\Http\Request实例的file方法来访问上传文件,该方法返回的对象是Symfony\Component\HttpFoundation\File\UploadedFile类的一个实例,该类继承自PHP标准库中,与文件交互的SplFileInfo类:

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

    可以使用hasFile方法,判断上传文件是否存在:

      if ($request->hasFile('photo')) {
          //
      }
    
  • 验证文件是否上传成功

    使用isValid方法判断文件在上传过程中是否出错:

      if ($request->file('photo')->isValid()){
          //
      }
      
    
  • 保存上传的文件

    如果要移动文件到新位置,必须使用move方法。该方法会将文件从暂存位置(根据机器PHP设定来确定)移动到指定的永久保存位置:

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

    $request->file('photo')->move($destinationPath, $fileName);
  • 其它文件方法

    UploadedFile实例中有很多可用的方法。可以到该类的API了解。

  • TOC
    {:toc}

5.3 控制器

5.3.1 简介

将所有的请求处理逻辑都放在单个routes.php中显示是不合理的,你也许还希望使用控制器类组织管理这些行为。

控制器可以将相关的HTTP请求封装到一个类中进行处理。

通常控制器存放在app/Http/Controllers目录中。


5.3.2 基本控制器

下面是一个基本控制器类的例子。所有的laravel控制器应该继承,laravel自带的控制器基类Controller:

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

namespace App\Http\Controllers;

use App\User;
use App\Http\Controllers\Controller;

class UserController extends Controller
{
/**
* 为指定用户显示详情
*
* @param int $id
* @return Response
*/
public function showProfile($id)
{
return view('user.profile', ['user' => User::findOrFail($id)]);
}
}

我们可以像这样定义指向控制器动作的路由:

1
Route::get('user/{id}', 'UserController@showProfile');

现在,如果一个请求匹配上面的路由URL,UserControllershowProfile方法就会被执行。

当然,路由参数也会被传递给这个方法。

5.3.2.1 控制器 & 命名空间

在命名时,没有使用完整的控制器命名,而只定义了App\Http\Controllers之后的部分。

默认RouteServiceProvider会将一个路由群组载入routes.php文件,并且该路由群组指定了,群组中路由控制器所在的命名空间。

如果在App\Http\Controllers目录下选择使用PHP命名空间 或者 组织控制器,只需要使用相对于App\Http\Controllers命令空间的指定类名即可。因此,如果完整控制器类是App\Http\Controllers\Photos\AdminController,可以像这样注册路由:

1
Route::get('foo', 'Photos\AdminController@method');

5.3.2.2 命名控制器路由

和闭包路由一样,可以指定控制器路由的名称:

1
Route::get('foo', ['uses' => 'FooController@method', 'as' => 'name']);

你可以使用辅助函数route来为己命名的控制器路由生成对应的URL:

1
$url = route('name');

5.3.3 控制器中间件

中间件可以分配给控制器路由:

1
2
3
4
Route::get('profile', [
'middleware' => 'auth',
'uses' => 'UserController@showProfile'
]);

但是,将中间件放在控制器构造函数中更方便,在控制器的构建函数中使用middleware方法可以很轻松的分配中间件给控制器。

甚至可以限定中间件应用到控制器的指定方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class UserController extends Controller
{
/**
* 实例化一个新的 UserController 实例
*
* @return void
*/
public function __construct()
{
$this->middleware('auth');
$this->middleware('log', ['only' => ['fooAction', 'barAction']]);
$this->middleware('subscribed', ['except' => ['fooAction', 'barAction']]);
}
}

5.3.4 RESTful 资源控制器

laravel的资源控制器使得构建围绕资源的RESTful控制器变得毫无痛苦。

例如,想要在应用中创建一个控制器,用于处理关于图片存储的HTTP请求,使用Artisan命令make:controller,可以快速创建这样的控制器:

php artisan make:controller PhotoController --resource

这个Artisan命令将会生成一个控制器文件app/Http/Controllers/PhotoController.php,这个控制器包含了每一个资源操作的方法。

接下来,可以为该控制器注册一个资源路由:

Route::resource('photo', 'PhotoController');

这个理由声明包含了处理图片资源RESTful 动作的多个路由,相应地,Artisan 生成的控制器也已经为这些动作设置了对应的处理方法。

5.3.4.1 资源控制器处理的动作

方法 路径 动作 路由名称
GET /photo index photo.index
GET /photo/create create photo.create
POST /photo store photo.store
GET /photo/{photo} show photo.show
GET /photo/{photo}/edit edit photo.edit
PUT/PATCH /photo/{photo} update photo.update
DELETE /photo/{photo} destroy photo.destroy

注意:HTTP表单不支持PUT、PATCH、DELETE请求,需要添加一个隐藏的_method文件来仿照这些动词。

1
<input type="hidden" name="_method" value="PUT">

5.3.4.2 只定义部分资源路由

声明资源路由时可以指定该路由处理的动作子集:

1
2
3
4
5
Route::resource('photo', 'PhotoController',
['only' => ['index', 'show']]);

Route::resource('photo', 'PhotoController',
['except' => ['create', 'store', 'update', 'destroy']]);

5.3.4.3 命名资源路由

默认情况下,所有资源控制器动作都有一个路由名称,然而,我们可以通过传入names数组来覆盖这些默认的名字:

1
2
Route::resource('photo', 'PhotoController',
['names' => ['create' => 'photo.build']]);

5.3.4.4 补充资源控制器

如果有必要在默认资源路由之外添加额外的路由到资源控制器,应该在调用Route::resource之前定义这些路由,否则,通过resource方法定义的路由可能无意中优于补充的额外路由:

1
2
Route::get('photos/popular', 'PhotoController@method');
Route::resource('photos', 'PhotoController');

5.3.5 依赖注入 & 控制器

5.3.5.1 构造函数注入

laravel使用服务容器解析所有的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
<?php

namespace App\Http\Controllers;

use Illuminate\Routing\Controller;
use App\Repositories\UserRepository;

class UserController extends Controller
{
/**
* The user repository instance.
*/
protected $users;

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

当然,还可以对任何的laravel contact 使用类别提示。若容器能解析它,就可以使用型别提示。

5.3.5.2 方法注入

除了构造函数注入外,还可以在控制器的动作方法中进行依赖的类型提示。

例如,可以对Illuminate\Http\Request 实例,其中的一个方法使用类别提示:

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 Illuminate\Http\Request;

class UserController extends Controller
{
/**
* 儲存一個新的使用者。
*
* @param Request $request
* @return Response
*/
public function store(Request $request)
{
$name = $request->input('name');

//
}
}

如果控制器方法期望输入路由参数,只需要将路由参数放到其他依赖之后,例如,如果你的路由定义如下:

1
Route::put('user/{id}', 'UserController@update');

你需要通过定义控制器方法如下所示来类型提示 Illuminate\Http\Request 并访问路由参数 id

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

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Routing\Controller;

class UserController extends Controller
{
/**
* 更新指定用户
*
* @param Request $request
* @param int $id
* @return Response
* @translator http://laravelacademy.org
*/
public function update(Request $request, $id)
{
//
}
}

5.3.6 路由缓存

使用控制器路由的重要性

注意:路由缓存不会作用于基于闭包的路由。要使用路由缓存,必须将闭包路由转化为控制器路由。

如果你的应用完全基于控制器路由,可以使用 Laravel 的路由缓存,使用路由缓存将会极大减少注册所有应用路由所花费的时间开销,在某些案例中,路由注册速度甚至能提高100倍!想要生成路由缓存,只需执行 Artisan 命令·route:cache:

php artisan route:cache

就这么简单!你的缓存路由文件现在取代 app/Http/routes.php 文件被使用,记住,如果你添加新的路由需要重新生成路由缓存。因此,只有在项目部署阶段才需要运行 route:cache 命令。

想要移除缓存路由文件,使用 route:clear 命令即可:

php artisan route:clear

0%